Compare commits
25 Commits
Author | SHA1 | Date | |
---|---|---|---|
669f12e822 | |||
8b870a8e46 | |||
9148f0595a | |||
ea7da1c9b9 | |||
3e81f54e99 | |||
65aa9f3c06 | |||
82ebd9c556 | |||
50aa071e2e | |||
807e1ff733 | |||
4146a348ab | |||
bd10b4e64d | |||
243ecddd42 | |||
d7b690621e | |||
60951330d1 | |||
7095197d07 | |||
3ee48e80ad | |||
00ad2b0563 | |||
a57005a49b | |||
d67a66662d | |||
c75c5bcd3b | |||
ad0864cddf | |||
9985c29a84 | |||
1dcaccdb6d | |||
35eb410051 | |||
10c43ecd59 |
174
CLAUDE.md
Normal file
174
CLAUDE.md
Normal file
@ -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
|
28
changelog.md
28
changelog.md
@ -1,5 +1,33 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 2025-06-27 - 1.10.1 - fix(modal)
|
||||||
|
Improve modal overscroll behavior by adding 'overscroll-behavior: contain' to content container
|
||||||
|
|
||||||
|
- Added 'overscroll-behavior: contain' to .modal .content to ensure proper scroll containment
|
||||||
|
- Applied overscroll-behavior in modal container for enhanced responsiveness on mobile and desktop
|
||||||
|
|
||||||
|
## 2025-06-26 - 1.10.0 - feat(dees-modal)
|
||||||
|
Add mobileFullscreen option to modals for full-screen mobile support
|
||||||
|
|
||||||
|
- Introduced a new boolean property 'mobileFullscreen' in ts_web/elements/dees-modal.ts
|
||||||
|
- Updated modal CSS under the media query to apply 'mobile-fullscreen' class, allowing full viewport modals on mobile devices
|
||||||
|
- Extended modal style rules to include adjustments for margin, border-radius, and maximum heights on smaller screens
|
||||||
|
|
||||||
|
## 2025-06-26 - 1.9.9 - fix(dees-input-multitoggle, dees-input-typelist)
|
||||||
|
Replace dynamic import with static import for demo functions in dees-input-multitoggle and dees-input-typelist
|
||||||
|
|
||||||
|
- Converted `await import('./dees-input-multitoggle.demo.js')` to a direct static import.
|
||||||
|
- Converted `await import('./dees-input-typelist.demo.js')` to a direct static import to improve build performance and clarity.
|
||||||
|
|
||||||
|
## 2025-06-26 - 1.9.8 - fix(deps, windowlayer)
|
||||||
|
Update dependency versions and adjust dees-windowlayer CSS to add pointer-events fix
|
||||||
|
|
||||||
|
- Bump @design.estate/dees-wcctools from ^1.0.98 to ^1.0.101
|
||||||
|
- Bump @tiptap packages from 2.22.3 to 2.23.0
|
||||||
|
- Bump lucide from ^0.522.0 to ^0.523.0
|
||||||
|
- Bump @git.zone/tsbundle from ^2.4.0 to ^2.5.1 and tswatch from ^2.0.37 to ^2.1.2
|
||||||
|
- Add 'pointer-events: none' to dees-windowlayer CSS to improve overlay behavior
|
||||||
|
|
||||||
## 2025-06-22 - 1.9.0 - feat(form-inputs)
|
## 2025-06-22 - 1.9.0 - feat(form-inputs)
|
||||||
Improve form input consistency and auto spacing across inputs and buttons
|
Improve form input consistency and auto spacing across inputs and buttons
|
||||||
|
|
||||||
|
26
package.json
26
package.json
@ -1,14 +1,14 @@
|
|||||||
{
|
{
|
||||||
"name": "@design.estate/dees-catalog",
|
"name": "@design.estate/dees-catalog",
|
||||||
"version": "1.9.7",
|
"version": "1.10.6",
|
||||||
"private": false,
|
"private": false,
|
||||||
"description": "A comprehensive library that provides dynamic web components for building sophisticated and modern web applications using JavaScript and TypeScript.",
|
"description": "A comprehensive library that provides dynamic web components for building sophisticated and modern web applications using JavaScript and TypeScript.",
|
||||||
"main": "dist_ts_web/index.js",
|
"main": "dist_ts_web/index.js",
|
||||||
"typings": "dist_ts_web/index.d.ts",
|
"typings": "dist_ts_web/index.d.ts",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "tstest test/ --web --verbose --timeout 30",
|
"test": "tstest test/ --web --verbose --timeout 30 --logfile",
|
||||||
"build": "tsbuild tsfolders --allowimplicitany && tsbundle element --production",
|
"build": "tsbuild tsfolders --allowimplicitany && tsbundle element --production --bundler esbuild",
|
||||||
"watch": "tswatch element",
|
"watch": "tswatch element",
|
||||||
"buildDocs": "tsdoc"
|
"buildDocs": "tsdoc"
|
||||||
},
|
},
|
||||||
@ -17,7 +17,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@design.estate/dees-domtools": "^2.3.3",
|
"@design.estate/dees-domtools": "^2.3.3",
|
||||||
"@design.estate/dees-element": "^2.0.45",
|
"@design.estate/dees-element": "^2.0.45",
|
||||||
"@design.estate/dees-wcctools": "^1.0.98",
|
"@design.estate/dees-wcctools": "^1.0.101",
|
||||||
"@fortawesome/fontawesome-svg-core": "^6.7.2",
|
"@fortawesome/fontawesome-svg-core": "^6.7.2",
|
||||||
"@fortawesome/free-brands-svg-icons": "^6.7.2",
|
"@fortawesome/free-brands-svg-icons": "^6.7.2",
|
||||||
"@fortawesome/free-regular-svg-icons": "^6.7.2",
|
"@fortawesome/free-regular-svg-icons": "^6.7.2",
|
||||||
@ -25,18 +25,18 @@
|
|||||||
"@push.rocks/smarti18n": "^1.0.4",
|
"@push.rocks/smarti18n": "^1.0.4",
|
||||||
"@push.rocks/smartpromise": "^4.2.0",
|
"@push.rocks/smartpromise": "^4.2.0",
|
||||||
"@push.rocks/smartstring": "^4.0.15",
|
"@push.rocks/smartstring": "^4.0.15",
|
||||||
"@tiptap/core": "^2.22.3",
|
"@tiptap/core": "^2.23.0",
|
||||||
"@tiptap/extension-link": "^2.22.3",
|
"@tiptap/extension-link": "^2.23.0",
|
||||||
"@tiptap/extension-text-align": "^2.22.3",
|
"@tiptap/extension-text-align": "^2.23.0",
|
||||||
"@tiptap/extension-typography": "^2.22.3",
|
"@tiptap/extension-typography": "^2.23.0",
|
||||||
"@tiptap/extension-underline": "^2.22.3",
|
"@tiptap/extension-underline": "^2.23.0",
|
||||||
"@tiptap/starter-kit": "^2.22.3",
|
"@tiptap/starter-kit": "^2.23.0",
|
||||||
"@tsclass/tsclass": "^9.2.0",
|
"@tsclass/tsclass": "^9.2.0",
|
||||||
"@webcontainer/api": "1.2.0",
|
"@webcontainer/api": "1.2.0",
|
||||||
"apexcharts": "^4.7.0",
|
"apexcharts": "^4.7.0",
|
||||||
"highlight.js": "11.11.1",
|
"highlight.js": "11.11.1",
|
||||||
"ibantools": "^4.5.1",
|
"ibantools": "^4.5.1",
|
||||||
"lucide": "^0.522.0",
|
"lucide": "^0.523.0",
|
||||||
"monaco-editor": "^0.52.2",
|
"monaco-editor": "^0.52.2",
|
||||||
"pdfjs-dist": "^4.10.38",
|
"pdfjs-dist": "^4.10.38",
|
||||||
"xterm": "^5.3.0",
|
"xterm": "^5.3.0",
|
||||||
@ -44,9 +44,9 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@git.zone/tsbuild": "^2.6.4",
|
"@git.zone/tsbuild": "^2.6.4",
|
||||||
"@git.zone/tsbundle": "^2.4.0",
|
"@git.zone/tsbundle": "^2.5.1",
|
||||||
"@git.zone/tstest": "^2.3.1",
|
"@git.zone/tstest": "^2.3.1",
|
||||||
"@git.zone/tswatch": "^2.0.37",
|
"@git.zone/tswatch": "^2.1.2",
|
||||||
"@push.rocks/projectinfo": "^5.0.2",
|
"@push.rocks/projectinfo": "^5.0.2",
|
||||||
"@push.rocks/tapbundle": "^6.0.3",
|
"@push.rocks/tapbundle": "^6.0.3",
|
||||||
"@types/node": "^22.0.0"
|
"@types/node": "^22.0.0"
|
||||||
|
711
pnpm-lock.yaml
generated
711
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@ -1,5 +1,5 @@
|
|||||||
!!! Please pay attention to the following points when writing the readme: !!!
|
!!! 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.
|
* Try to list all components in a summary.
|
||||||
* Then list all components with a short description.
|
* Then list all components with a short description.
|
||||||
|
|
||||||
|
@ -3,6 +3,6 @@
|
|||||||
*/
|
*/
|
||||||
export const commitinfo = {
|
export const commitinfo = {
|
||||||
name: '@design.estate/dees-catalog',
|
name: '@design.estate/dees-catalog',
|
||||||
version: '1.9.0',
|
version: '1.10.1',
|
||||||
description: 'A comprehensive library that provides dynamic web components for building sophisticated and modern web applications using JavaScript and TypeScript.'
|
description: 'A comprehensive library that provides dynamic web components for building sophisticated and modern web applications using JavaScript and TypeScript.'
|
||||||
}
|
}
|
||||||
|
37
ts_web/elements/00fonts.ts
Normal file
37
ts_web/elements/00fonts.ts
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import { unsafeCSS } from '@design.estate/dees-element';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Geist Sans font family - Main font for the design system
|
||||||
|
* Already available in the environment, no need to load
|
||||||
|
*/
|
||||||
|
export const geistSansFont = 'Geist Sans';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Intel One Mono font family - Monospace font for code and technical content
|
||||||
|
* Already available in the environment, no need to load
|
||||||
|
*/
|
||||||
|
export const intelOneMonoFont = 'Intel One Mono';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Complete font family stacks with fallbacks
|
||||||
|
*/
|
||||||
|
export const geistFontFamily = `'${geistSansFont}', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif`;
|
||||||
|
|
||||||
|
export const monoFontFamily = `'${intelOneMonoFont}', 'SF Mono', 'Monaco', 'Inconsolata', 'Fira Code', 'Fira Mono', 'Droid Sans Mono', 'Courier New', monospace`;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CSS-ready font family values using unsafeCSS
|
||||||
|
* Use these in component styles
|
||||||
|
*/
|
||||||
|
export const cssGeistFontFamily = unsafeCSS(geistFontFamily);
|
||||||
|
export const cssMonoFontFamily = unsafeCSS(monoFontFamily);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base font styles that can be applied to components
|
||||||
|
*/
|
||||||
|
export const baseFontStyles = unsafeCSS(`
|
||||||
|
font-family: ${geistFontFamily};
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
font-feature-settings: 'cv11', 'tnum', 'cv05' 1;
|
||||||
|
`);
|
@ -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`
|
export const demoFunc = () => html`
|
||||||
|
<dees-demowrapper>
|
||||||
<style>
|
<style>
|
||||||
${css`
|
${css`
|
||||||
h3 {
|
.demo-container {
|
||||||
margin-top: 32px;
|
display: flex;
|
||||||
margin-bottom: 16px;
|
flex-direction: column;
|
||||||
color: #333;
|
gap: 24px;
|
||||||
|
padding: 24px;
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (prefers-color-scheme: dark) {
|
dees-panel {
|
||||||
h3 {
|
margin-bottom: 24px;
|
||||||
color: #ccc;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-demo {
|
dees-panel:last-child {
|
||||||
background: #f5f5f5;
|
margin-bottom: 0;
|
||||||
padding: 20px;
|
|
||||||
border-radius: 8px;
|
|
||||||
margin: 20px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (prefers-color-scheme: dark) {
|
|
||||||
.form-demo {
|
|
||||||
background: #1a1a1a;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.button-group {
|
.button-group {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vertical-group {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
max-width: 300px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.horizontal-group {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
gap: 16px;
|
gap: 16px;
|
||||||
margin: 20px 0;
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.demo-output {
|
||||||
|
margin-top: 16px;
|
||||||
|
padding: 12px;
|
||||||
|
background: ${cssManager.bdTheme('hsl(210 40% 96.1%)', 'hsl(215 20.2% 16.8%)')};
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-family: monospace;
|
||||||
|
color: ${cssManager.bdTheme('hsl(215.3 25% 8.8%)', 'hsl(210 40% 98%)')};
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
margin: 8px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.code-snippet {
|
||||||
|
background: ${cssManager.bdTheme('hsl(210 40% 96.1%)', 'hsl(215 20.2% 11.8%)')};
|
||||||
|
padding: 8px 12px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-family: monospace;
|
||||||
|
font-size: 13px;
|
||||||
|
display: inline-block;
|
||||||
|
margin: 4px 0;
|
||||||
}
|
}
|
||||||
`}
|
`}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<h3>Button Types</h3>
|
<div class="demo-container">
|
||||||
<dees-button>This is a slotted Text</dees-button>
|
<dees-panel .title=${'1. Button Variants'} .subtitle=${'Different visual styles for various use cases'}>
|
||||||
<p>
|
<div class="button-group">
|
||||||
<dees-button text="Highlighted: This text shows" type="highlighted">Highlighted</dees-button>
|
<dees-button type="default">Default</dees-button>
|
||||||
</p>
|
<dees-button type="secondary">Secondary</dees-button>
|
||||||
<p><dees-button type="discreet">This is discreete button</dees-button></p>
|
<dees-button type="destructive">Destructive</dees-button>
|
||||||
<p><dees-button disabled>This is a disabled button</dees-button></p>
|
<dees-button type="outline">Outline</dees-button>
|
||||||
<p><dees-button type="big">This is a slotted Text</dees-button></p>
|
<dees-button type="ghost">Ghost</dees-button>
|
||||||
|
<dees-button type="link">Link Button</dees-button>
|
||||||
|
</div>
|
||||||
|
</dees-panel>
|
||||||
|
|
||||||
<h3>Button States</h3>
|
<dees-panel .title=${'2. Button Sizes'} .subtitle=${'Multiple sizes for different contexts and use cases'}>
|
||||||
<p><dees-button status="normal">Normal Status</dees-button></p>
|
<div class="button-group">
|
||||||
<p><dees-button disabled status="pending">Pending Status</dees-button></p>
|
<dees-button size="sm">Small Button</dees-button>
|
||||||
<p><dees-button disabled status="success">Success Status</dees-button></p>
|
<dees-button size="default">Default Size</dees-button>
|
||||||
<p><dees-button disabled status="error">Error Status</dees-button></p>
|
<dees-button size="lg">Large Button</dees-button>
|
||||||
|
<dees-button size="icon" type="outline" .text=${'🚀'}></dees-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<h3>Buttons in Forms (Auto-spacing)</h3>
|
<div class="button-group" style="margin-top: 16px;">
|
||||||
<div class="form-demo">
|
<dees-button size="sm" type="secondary">Small Secondary</dees-button>
|
||||||
<dees-form>
|
<dees-button size="default" type="destructive">Default Destructive</dees-button>
|
||||||
<dees-input-text label="Name" key="name"></dees-input-text>
|
<dees-button size="lg" type="outline">Large Outline</dees-button>
|
||||||
<dees-input-text label="Email" key="email"></dees-input-text>
|
</div>
|
||||||
<dees-button>Save Draft</dees-button>
|
</dees-panel>
|
||||||
<dees-button type="highlighted">Save and Continue</dees-button>
|
|
||||||
|
<dees-panel .title=${'3. Buttons with Icons'} .subtitle=${'Combining icons with text for enhanced visual communication'}>
|
||||||
|
<div class="icon-row">
|
||||||
|
<dees-button>
|
||||||
|
<dees-icon iconFA="faPlus"></dees-icon>
|
||||||
|
Add Item
|
||||||
|
</dees-button>
|
||||||
|
<dees-button type="destructive">
|
||||||
|
<dees-icon iconFA="faTrash"></dees-icon>
|
||||||
|
Delete
|
||||||
|
</dees-button>
|
||||||
|
<dees-button type="outline">
|
||||||
|
<dees-icon iconFA="faDownload"></dees-icon>
|
||||||
|
Download
|
||||||
|
</dees-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="icon-row">
|
||||||
|
<dees-button type="secondary" size="sm">
|
||||||
|
<dees-icon iconFA="faCog"></dees-icon>
|
||||||
|
Settings
|
||||||
|
</dees-button>
|
||||||
|
<dees-button type="ghost">
|
||||||
|
<dees-icon iconFA="faChevronLeft"></dees-icon>
|
||||||
|
Back
|
||||||
|
</dees-button>
|
||||||
|
<dees-button type="ghost">
|
||||||
|
Next
|
||||||
|
<dees-icon iconFA="faChevronRight"></dees-icon>
|
||||||
|
</dees-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="icon-row">
|
||||||
|
<dees-button size="icon" type="default">
|
||||||
|
<dees-icon iconFA="faPlus"></dees-icon>
|
||||||
|
</dees-button>
|
||||||
|
<dees-button size="icon" type="secondary">
|
||||||
|
<dees-icon iconFA="faCog"></dees-icon>
|
||||||
|
</dees-button>
|
||||||
|
<dees-button size="icon" type="outline">
|
||||||
|
<dees-icon iconFA="faSearch"></dees-icon>
|
||||||
|
</dees-button>
|
||||||
|
<dees-button size="icon" type="ghost">
|
||||||
|
<dees-icon iconFA="faEllipsisV"></dees-icon>
|
||||||
|
</dees-button>
|
||||||
|
<dees-button size="icon" type="destructive">
|
||||||
|
<dees-icon iconFA="faTrash"></dees-icon>
|
||||||
|
</dees-button>
|
||||||
|
</div>
|
||||||
|
</dees-panel>
|
||||||
|
|
||||||
|
<dees-panel .title=${'4. Button States'} .subtitle=${'Different states to indicate button status and loading conditions'}>
|
||||||
|
<div class="button-group">
|
||||||
|
<dees-button status="normal">Normal</dees-button>
|
||||||
|
<dees-button status="pending">Processing...</dees-button>
|
||||||
|
<dees-button status="success">Success!</dees-button>
|
||||||
|
<dees-button status="error">Error!</dees-button>
|
||||||
|
<dees-button disabled>Disabled</dees-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="button-group" style="margin-top: 16px;">
|
||||||
|
<dees-button type="secondary" status="pending" size="sm">Small Loading</dees-button>
|
||||||
|
<dees-button type="outline" status="pending">Default Loading</dees-button>
|
||||||
|
<dees-button type="destructive" status="pending" size="lg">Large Loading</dees-button>
|
||||||
|
</div>
|
||||||
|
</dees-panel>
|
||||||
|
|
||||||
|
<dees-panel .title=${'5. Event Handling'} .subtitle=${'Interactive examples with click event handling'}>
|
||||||
|
<div class="button-group">
|
||||||
|
<dees-button
|
||||||
|
@clicked=${() => {
|
||||||
|
const output = document.querySelector('#click-output');
|
||||||
|
if (output) {
|
||||||
|
output.textContent = `Clicked: Default button at ${new Date().toLocaleTimeString()}`;
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Click Me
|
||||||
|
</dees-button>
|
||||||
|
|
||||||
|
<dees-button
|
||||||
|
type="secondary"
|
||||||
|
.eventDetailData=${'custom-data-123'}
|
||||||
|
@clicked=${(e: CustomEvent) => {
|
||||||
|
const output = document.querySelector('#click-output');
|
||||||
|
if (output) {
|
||||||
|
output.textContent = `Clicked: Secondary button with data: ${e.detail.data}`;
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Click with Data
|
||||||
|
</dees-button>
|
||||||
|
|
||||||
|
<dees-button
|
||||||
|
type="destructive"
|
||||||
|
@clicked=${async () => {
|
||||||
|
const output = document.querySelector('#click-output');
|
||||||
|
if (output) {
|
||||||
|
output.textContent = 'Processing...';
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 2000));
|
||||||
|
output.textContent = 'Action completed!';
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Async Action
|
||||||
|
</dees-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="click-output" class="demo-output">
|
||||||
|
<em>Click a button to see the result...</em>
|
||||||
|
</div>
|
||||||
|
</dees-panel>
|
||||||
|
|
||||||
|
<dees-panel .title=${'6. Form Integration'} .subtitle=${'Buttons working within forms with automatic spacing'}>
|
||||||
|
<dees-form @formData=${(e: CustomEvent) => {
|
||||||
|
const output = document.querySelector('#form-output');
|
||||||
|
if (output) {
|
||||||
|
output.innerHTML = '<strong>Form submitted with data:</strong><br>' +
|
||||||
|
JSON.stringify(e.detail.data, null, 2);
|
||||||
|
}
|
||||||
|
}}>
|
||||||
|
<dees-input-text label="Name" key="name" required></dees-input-text>
|
||||||
|
<dees-input-text label="Email" key="email" type="email" required></dees-input-text>
|
||||||
|
<dees-input-text label="Message" key="message" isMultiline></dees-input-text>
|
||||||
|
|
||||||
|
<dees-button type="secondary">Save Draft</dees-button>
|
||||||
|
<dees-button type="ghost">Cancel</dees-button>
|
||||||
<dees-form-submit>Submit Form</dees-form-submit>
|
<dees-form-submit>Submit Form</dees-form-submit>
|
||||||
</dees-form>
|
</dees-form>
|
||||||
</div>
|
|
||||||
|
|
||||||
<h3>Buttons Outside Forms (No auto-spacing)</h3>
|
<div id="form-output" class="demo-output" style="white-space: pre-wrap;">
|
||||||
|
<em>Submit the form to see the data...</em>
|
||||||
|
</div>
|
||||||
|
</dees-panel>
|
||||||
|
|
||||||
|
<dees-panel .title=${'7. Backward Compatibility'} .subtitle=${'Old button types are automatically mapped to new variants'}>
|
||||||
<div class="button-group">
|
<div class="button-group">
|
||||||
<dees-button>Button 1</dees-button>
|
<dees-button type="normal">Normal → Default</dees-button>
|
||||||
<dees-button>Button 2</dees-button>
|
<dees-button type="highlighted">Highlighted → Destructive</dees-button>
|
||||||
<dees-button>Button 3</dees-button>
|
<dees-button type="discreet">Discreet → Outline</dees-button>
|
||||||
|
<dees-button type="big">Big → Large Size</dees-button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h3>Manual Form Spacing</h3>
|
<p style="margin-top: 16px; font-size: 14px; color: ${cssManager.bdTheme('hsl(215.4 16.3% 56.9%)', 'hsl(215 20.2% 55.1%)')};">
|
||||||
<div>
|
These legacy type values are maintained for backward compatibility but we recommend using the new variant system.
|
||||||
<dees-button inside-form="true">Manually spaced button 1</dees-button>
|
</p>
|
||||||
<dees-button inside-form="true">Manually spaced button 2</dees-button>
|
</dees-panel>
|
||||||
|
|
||||||
|
<dees-panel .title=${'8. Advanced Examples'} .subtitle=${'Complex button configurations and real-world use cases'}>
|
||||||
|
<div class="horizontal-group">
|
||||||
|
<div class="vertical-group">
|
||||||
|
<h4 style="margin: 0 0 8px 0; font-size: 14px; font-weight: 500;">Action Group</h4>
|
||||||
|
<dees-button type="default" size="sm">
|
||||||
|
<dees-icon iconFA="faSave"></dees-icon>
|
||||||
|
Save Changes
|
||||||
|
</dees-button>
|
||||||
|
<dees-button type="secondary" size="sm">
|
||||||
|
<dees-icon iconFA="faUndo"></dees-icon>
|
||||||
|
Discard
|
||||||
|
</dees-button>
|
||||||
|
<dees-button type="ghost" size="sm">
|
||||||
|
<dees-icon iconFA="faQuestionCircle"></dees-icon>
|
||||||
|
Help
|
||||||
|
</dees-button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="vertical-group">
|
||||||
|
<h4 style="margin: 0 0 8px 0; font-size: 14px; font-weight: 500;">Danger Zone</h4>
|
||||||
|
<dees-button type="destructive" size="sm">
|
||||||
|
<dees-icon iconFA="faTrash"></dees-icon>
|
||||||
|
Delete Account
|
||||||
|
</dees-button>
|
||||||
|
<dees-button type="outline" size="sm">
|
||||||
|
<dees-icon iconFA="faArchive"></dees-icon>
|
||||||
|
Archive Data
|
||||||
|
</dees-button>
|
||||||
|
<dees-button type="ghost" size="sm" disabled>
|
||||||
|
<dees-icon iconFA="faBan"></dees-icon>
|
||||||
|
Not Available
|
||||||
|
</dees-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="margin-top: 24px;">
|
||||||
|
<h4 style="margin: 0 0 8px 0; font-size: 14px; font-weight: 500;">Code Example:</h4>
|
||||||
|
<div class="code-snippet">
|
||||||
|
<dees-button type="default" size="sm" @clicked="\${handleClick}"><br>
|
||||||
|
<dees-icon iconFA="faSave"></dees-icon><br>
|
||||||
|
Save Changes<br>
|
||||||
|
</dees-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</dees-panel>
|
||||||
|
</div>
|
||||||
|
</dees-demowrapper>
|
||||||
`;
|
`;
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import { demoFunc } from './dees-button.demo.js';
|
|
||||||
import {
|
import {
|
||||||
customElement,
|
customElement,
|
||||||
html,
|
html,
|
||||||
@ -12,6 +11,7 @@ import {
|
|||||||
} from '@design.estate/dees-element';
|
} from '@design.estate/dees-element';
|
||||||
|
|
||||||
import * as domtools from '@design.estate/dees-domtools';
|
import * as domtools from '@design.estate/dees-domtools';
|
||||||
|
import { demoFunc } from './dees-button.demo.js';
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface HTMLElementTagNameMap {
|
interface HTMLElementTagNameMap {
|
||||||
@ -48,7 +48,12 @@ export class DeesButton extends DeesElement {
|
|||||||
@property({
|
@property({
|
||||||
type: String
|
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({
|
@property({
|
||||||
type: String
|
type: String
|
||||||
@ -77,25 +82,23 @@ export class DeesButton extends DeesElement {
|
|||||||
cssManager.defaultStyles,
|
cssManager.defaultStyles,
|
||||||
css`
|
css`
|
||||||
:host {
|
:host {
|
||||||
display: block;
|
display: inline-block;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
font-family: 'Geist Sans', 'monospace';
|
font-family: inherit;
|
||||||
}
|
}
|
||||||
:host([hidden]) {
|
:host([hidden]) {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Form spacing styles */
|
/* Form spacing styles */
|
||||||
/* Default vertical form layout */
|
|
||||||
:host([inside-form]) {
|
:host([inside-form]) {
|
||||||
margin-bottom: 16px; /* Using standard 16px like inputs */
|
margin-bottom: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
:host([inside-form]:last-child) {
|
:host([inside-form]:last-child) {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Horizontal form layout - auto-detected via parent */
|
|
||||||
dees-form[horizontal-layout] :host([inside-form]) {
|
dees-form[horizontal-layout] :host([inside-form]) {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin-right: 16px;
|
margin-right: 16px;
|
||||||
@ -107,114 +110,260 @@ export class DeesButton extends DeesElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.button {
|
.button {
|
||||||
transition: all 0.1s , color 0s;
|
|
||||||
position: relative;
|
position: relative;
|
||||||
font-size: 14px;
|
display: inline-flex;
|
||||||
font-weight: 400;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
background: ${cssManager.bdTheme('#fff', '#333')};
|
justify-content: center;
|
||||||
box-shadow: ${cssManager.bdTheme('0px 1px 3px rgba(0,0,0,0.3)', 'none')};
|
white-space: nowrap;
|
||||||
border: 1px solid ${cssManager.bdTheme('#eee', '#333')};
|
border-radius: 6px;
|
||||||
border-top: ${cssManager.bdTheme('1px solid #eee', '1px solid #444')};
|
font-weight: 500;
|
||||||
border-radius: 4px;
|
transition: all 0.15s ease;
|
||||||
height: 40px;
|
cursor: pointer;
|
||||||
padding: 0px 8px;
|
|
||||||
min-width: 100px;
|
|
||||||
user-select: none;
|
user-select: none;
|
||||||
color: ${cssManager.bdTheme('#333', ' #ccc')};
|
outline: none;
|
||||||
max-width: 500px;
|
letter-spacing: -0.01em;
|
||||||
|
gap: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.button:hover {
|
/* Size variants */
|
||||||
background: #0050b9;
|
.button.size-default {
|
||||||
color: #ffffff;
|
height: 36px;
|
||||||
border: 1px solid #0050b9;
|
padding: 0 16px;
|
||||||
border-top: 1px solid #0050b9;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.button:active {
|
.button.size-sm {
|
||||||
background: #0069f2;
|
height: 32px;
|
||||||
border-top: 1px solid #0069f2;
|
padding: 0 12px;
|
||||||
|
font-size: 13px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.button.highlighted {
|
.button.size-lg {
|
||||||
background: #e4002b;
|
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;
|
border: none;
|
||||||
color: #fff;
|
text-decoration: underline;
|
||||||
|
text-decoration-color: transparent;
|
||||||
|
height: auto;
|
||||||
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.button.highlighted:hover {
|
.button.link:hover:not(.disabled) {
|
||||||
background: #b50021;
|
text-decoration-color: currentColor;
|
||||||
border: none;
|
|
||||||
color: #fff;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.button.discreet {
|
/* Status states */
|
||||||
background: none;
|
.button.pending,
|
||||||
border: 1px solid #9b9b9e;
|
.button.success,
|
||||||
color: ${cssManager.bdTheme('#000', '#fff')};
|
.button.error {
|
||||||
|
pointer-events: none;
|
||||||
|
padding-left: 36px; /* Space for spinner */
|
||||||
}
|
}
|
||||||
|
|
||||||
.button.discreet:hover {
|
.button.size-sm.pending,
|
||||||
background: ${cssManager.bdTheme('rgba(0, 0, 0, 0.1)', 'rgba(255, 255, 255, 0.1)')};
|
.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.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 {
|
.button.disabled {
|
||||||
background: ${cssManager.bdTheme('#ffffff00', '#11111100')};
|
opacity: 0.5;
|
||||||
border: 1px dashed ${cssManager.bdTheme('#666666', '#666666')};
|
cursor: not-allowed;
|
||||||
color: #9b9b9e;
|
pointer-events: none;
|
||||||
cursor: default;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Hidden state */
|
||||||
.button.hidden {
|
.button.hidden {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.button.big {
|
/* Focus state */
|
||||||
width: 300px;
|
.button:focus-visible {
|
||||||
line-height: 48px;
|
outline: 2px solid ${cssManager.bdTheme('hsl(222.2 47.4% 51.2%)', 'hsl(213.1 93.9% 67.8%)')};
|
||||||
font-size: 16px;
|
outline-offset: 2px;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Loading spinner */
|
||||||
dees-spinner {
|
dees-spinner {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 10px;
|
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 {
|
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`
|
return html`
|
||||||
<div
|
<div
|
||||||
class="button ${this.isHidden ? 'hidden' : 'block'} ${this.type} ${this.status} ${this.disabled
|
class="button ${this.isHidden ? 'hidden' : ''} ${actualType} size-${actualSize} ${this.status} ${this.disabled
|
||||||
? 'disabled'
|
? 'disabled'
|
||||||
: null}"
|
: ''}"
|
||||||
@click="${this.dispatchClick}"
|
@click="${this.dispatchClick}"
|
||||||
>
|
>
|
||||||
${this.status === 'normal' ? html``: html`
|
${this.status === 'normal' ? html``: html`
|
||||||
<dees-spinner .bnw=${true} status="${this.status}"></dees-spinner>
|
<dees-spinner
|
||||||
|
.bnw=${true}
|
||||||
|
status="${this.status}"
|
||||||
|
size="${actualSize === 'sm' ? 14 : actualSize === 'lg' ? 18 : 16}"
|
||||||
|
></dees-spinner>
|
||||||
`}
|
`}
|
||||||
<div class="textbox">${this.text || html`<slot>Button</slot>`}</div>
|
<div class="textbox">${this.text || html`<slot>Button</slot>`}</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { html, css } from '@design.estate/dees-element';
|
import { html, css, cssManager } from '@design.estate/dees-element';
|
||||||
import type { DeesChartArea } from './dees-chart-area.js';
|
import type { DeesChartArea } from './dees-chart-area.js';
|
||||||
import '@design.estate/dees-wcctools/demotools';
|
import '@design.estate/dees-wcctools/demotools';
|
||||||
|
|
||||||
@ -402,7 +402,7 @@ export const demoFunc = () => {
|
|||||||
${css`
|
${css`
|
||||||
.demoBox {
|
.demoBox {
|
||||||
position: relative;
|
position: relative;
|
||||||
background: #000000;
|
background: ${cssManager.bdTheme('hsl(0 0% 95%)', 'hsl(0 0% 9%)')};
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 40px;
|
padding: 40px;
|
||||||
@ -425,9 +425,9 @@ export const demoFunc = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.info {
|
.info {
|
||||||
color: #666;
|
color: ${cssManager.bdTheme('hsl(215.4 16.3% 56.9%)', 'hsl(215 20.2% 55.1%)')};
|
||||||
font-size: 11px;
|
font-size: 12px;
|
||||||
font-family: 'Geist Sans', sans-serif;
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Geist Sans', sans-serif;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
margin-top: 8px;
|
margin-top: 8px;
|
||||||
}
|
}
|
||||||
|
@ -61,6 +61,23 @@ export class DeesChartArea extends DeesElement {
|
|||||||
private resizeTimeout: number;
|
private resizeTimeout: number;
|
||||||
private internalChartData: ApexAxisChartSeries = [];
|
private internalChartData: ApexAxisChartSeries = [];
|
||||||
private autoScrollTimer: number | null = null;
|
private autoScrollTimer: number | null = null;
|
||||||
|
private readonly DEBUG_RESIZE = false; // Set to true to enable resize debugging
|
||||||
|
|
||||||
|
// Chart color schemes
|
||||||
|
private readonly CHART_COLORS = {
|
||||||
|
dark: [
|
||||||
|
'hsl(217.2 91.2% 59.8%)', // Blue
|
||||||
|
'hsl(173.4 80.4% 40%)', // Teal
|
||||||
|
'hsl(280.3 87.4% 66.7%)', // Purple
|
||||||
|
'hsl(24.6 95% 53.1%)', // Orange
|
||||||
|
],
|
||||||
|
light: [
|
||||||
|
'hsl(222.2 47.4% 51.2%)', // Blue (shadcn primary)
|
||||||
|
'hsl(142.1 76.2% 36.3%)', // Green (shadcn success)
|
||||||
|
'hsl(280.3 47.7% 50.2%)', // Purple (muted)
|
||||||
|
'hsl(20.5 90.2% 48.2%)', // Orange (shadcn destructive variant)
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
@ -73,46 +90,72 @@ export class DeesChartArea extends DeesElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.resizeTimeout = window.setTimeout(() => {
|
this.resizeTimeout = window.setTimeout(() => {
|
||||||
for (let entry of entries) {
|
// Simply resize if we have a chart, since we're only observing the mainbox
|
||||||
if (entry.target.classList.contains('mainbox') && this.chart) {
|
if (this.chart) {
|
||||||
this.resizeChart();
|
// Log resize event for debugging
|
||||||
|
if (this.DEBUG_RESIZE && entries.length > 0) {
|
||||||
|
const entry = entries[0];
|
||||||
|
console.log('DeesChartArea - Resize detected:', {
|
||||||
|
width: entry.contentRect.width,
|
||||||
|
height: entry.contentRect.height
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
this.resizeChart();
|
||||||
}
|
}
|
||||||
}, 100); // 100ms debounce
|
}, 100); // 100ms debounce
|
||||||
});
|
});
|
||||||
|
|
||||||
this.registerStartupFunction(async () => {
|
// Note: ResizeObserver is now set up after chart initialization in firstUpdated()
|
||||||
this.updateComplete.then(() => {
|
// to ensure proper timing and avoid race conditions
|
||||||
const mainbox = this.shadowRoot.querySelector('.mainbox');
|
|
||||||
if (mainbox) {
|
|
||||||
this.resizeObserver.observe(mainbox);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
this.registerGarbageFunction(async () => {
|
this.registerGarbageFunction(async () => {
|
||||||
if (this.resizeTimeout) {
|
if (this.resizeTimeout) {
|
||||||
clearTimeout(this.resizeTimeout);
|
clearTimeout(this.resizeTimeout);
|
||||||
}
|
}
|
||||||
|
if (this.resizeObserver) {
|
||||||
this.resizeObserver.disconnect();
|
this.resizeObserver.disconnect();
|
||||||
|
}
|
||||||
this.stopAutoScroll();
|
this.stopAutoScroll();
|
||||||
|
|
||||||
|
// Critical: Destroy chart instance to prevent memory leak
|
||||||
|
if (this.chart) {
|
||||||
|
try {
|
||||||
|
this.chart.destroy();
|
||||||
|
this.chart = null;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error destroying chart:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async connectedCallback() {
|
||||||
|
super.connectedCallback();
|
||||||
|
|
||||||
|
// Trigger resize when element is connected to DOM
|
||||||
|
// This helps with dynamically added charts
|
||||||
|
if (this.chart) {
|
||||||
|
// Wait a frame for layout to settle
|
||||||
|
await new Promise(resolve => requestAnimationFrame(resolve));
|
||||||
|
await this.resizeChart();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static styles = [
|
public static styles = [
|
||||||
cssManager.defaultStyles,
|
cssManager.defaultStyles,
|
||||||
css`
|
css`
|
||||||
:host {
|
:host {
|
||||||
font-family: 'Geist Sans', sans-serif;
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif;
|
||||||
color: #ccc;
|
color: ${cssManager.bdTheme('hsl(0 0% 3.9%)', 'hsl(0 0% 98%)')};
|
||||||
font-weight: 600;
|
font-weight: 400;
|
||||||
font-size: 12px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
.mainbox {
|
.mainbox {
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 400px;
|
height: 400px;
|
||||||
background: #111;
|
background: ${cssManager.bdTheme('hsl(0 0% 100%)', 'hsl(0 0% 3.9%)')};
|
||||||
|
border: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')};
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
@ -122,9 +165,13 @@ export class DeesChartArea extends DeesElement {
|
|||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
text-align: center;
|
text-align: left;
|
||||||
padding-top: 16px;
|
padding: 16px 24px;
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
letter-spacing: -0.01em;
|
||||||
|
color: ${cssManager.bdTheme('hsl(0 0% 20%)', 'hsl(0 0% 63.9%)')};
|
||||||
}
|
}
|
||||||
.chartContainer {
|
.chartContainer {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@ -132,8 +179,22 @@ export class DeesChartArea extends DeesElement {
|
|||||||
left: 0px;
|
left: 0px;
|
||||||
bottom: 0px;
|
bottom: 0px;
|
||||||
right: 0px;
|
right: 0px;
|
||||||
padding: 32px 16px 16px 0px;
|
padding: 44px 16px 16px 0px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
background: transparent; /* Ensure container doesn't override chart background */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ApexCharts theme overrides */
|
||||||
|
.apexcharts-canvas {
|
||||||
|
background: transparent !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.apexcharts-inner {
|
||||||
|
background: transparent !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.apexcharts-graphical {
|
||||||
|
background: transparent !important;
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
@ -199,12 +260,17 @@ export class DeesChartArea extends DeesElement {
|
|||||||
// Store internal data
|
// Store internal data
|
||||||
this.internalChartData = chartSeries;
|
this.internalChartData = chartSeries;
|
||||||
|
|
||||||
|
// Get current theme
|
||||||
|
const isDark = !this.goBright;
|
||||||
|
const theme = isDark ? 'dark' : 'light';
|
||||||
|
|
||||||
var options: ApexCharts.ApexOptions = {
|
var options: ApexCharts.ApexOptions = {
|
||||||
series: chartSeries,
|
series: chartSeries,
|
||||||
chart: {
|
chart: {
|
||||||
width: initialWidth || 100, // Use actual width or fallback
|
width: initialWidth || 100, // Use actual width or fallback
|
||||||
height: initialHeight || 100, // Use actual height or fallback
|
height: initialHeight || 100, // Use actual height or fallback
|
||||||
type: 'area',
|
type: 'area',
|
||||||
|
background: 'transparent', // Transparent background to inherit from container
|
||||||
toolbar: {
|
toolbar: {
|
||||||
show: false, // This line disables the toolbar
|
show: false, // This line disables the toolbar
|
||||||
},
|
},
|
||||||
@ -220,12 +286,18 @@ export class DeesChartArea extends DeesElement {
|
|||||||
speed: 350
|
speed: 350
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
zoom: {
|
||||||
|
enabled: false, // Disable zoom for cleaner interaction
|
||||||
|
},
|
||||||
|
selection: {
|
||||||
|
enabled: false, // Disable selection
|
||||||
|
},
|
||||||
},
|
},
|
||||||
dataLabels: {
|
dataLabels: {
|
||||||
enabled: false,
|
enabled: false,
|
||||||
},
|
},
|
||||||
stroke: {
|
stroke: {
|
||||||
width: 1,
|
width: 2,
|
||||||
curve: 'smooth',
|
curve: 'smooth',
|
||||||
},
|
},
|
||||||
xaxis: {
|
xaxis: {
|
||||||
@ -234,8 +306,10 @@ export class DeesChartArea extends DeesElement {
|
|||||||
format: 'HH:mm:ss', // Time formatting with seconds
|
format: 'HH:mm:ss', // Time formatting with seconds
|
||||||
datetimeUTC: false,
|
datetimeUTC: false,
|
||||||
style: {
|
style: {
|
||||||
colors: '#9e9e9e', // Label color
|
colors: [isDark ? 'hsl(0 0% 63.9%)' : 'hsl(0 0% 20%)'], // Label color
|
||||||
fontSize: '11px',
|
fontSize: '12px',
|
||||||
|
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif',
|
||||||
|
fontWeight: '400',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
axisBorder: {
|
axisBorder: {
|
||||||
@ -251,8 +325,10 @@ export class DeesChartArea extends DeesElement {
|
|||||||
labels: {
|
labels: {
|
||||||
formatter: this.yAxisFormatter,
|
formatter: this.yAxisFormatter,
|
||||||
style: {
|
style: {
|
||||||
colors: '#9e9e9e', // Label color
|
colors: [isDark ? 'hsl(0 0% 63.9%)' : 'hsl(0 0% 20%)'], // Label color
|
||||||
fontSize: '12px',
|
fontSize: '12px',
|
||||||
|
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif',
|
||||||
|
fontWeight: '400',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
axisBorder: {
|
axisBorder: {
|
||||||
@ -269,14 +345,30 @@ export class DeesChartArea extends DeesElement {
|
|||||||
x: {
|
x: {
|
||||||
format: 'dd/MM/yy HH:mm',
|
format: 'dd/MM/yy HH:mm',
|
||||||
},
|
},
|
||||||
custom: function ({ series, dataPointIndex, w }: any) {
|
custom: ({ series, dataPointIndex, w }: any) => {
|
||||||
// Iterate through each series and get its value
|
// Iterate through each series and get its value
|
||||||
let tooltipContent = `<div style="padding: 10px; background: #1e1e2f; color: white; border-radius: 5px;">`;
|
// Note: We can't access component instance here, so we'll use w.config.theme.mode
|
||||||
|
const currentTheme = w.config.theme.mode;
|
||||||
|
const isDarkMode = currentTheme === 'dark';
|
||||||
|
const bgColor = isDarkMode ? 'hsl(0 0% 9%)' : 'hsl(0 0% 100%)';
|
||||||
|
const textColor = isDarkMode ? 'hsl(0 0% 95%)' : 'hsl(0 0% 9%)';
|
||||||
|
const borderColor = isDarkMode ? 'hsl(0 0% 14.9%)' : 'hsl(0 0% 89.8%)';
|
||||||
|
|
||||||
|
// Get formatter from chart config
|
||||||
|
const formatter = w.config.yaxis[0]?.labels?.formatter || ((val: number) => val.toString());
|
||||||
|
|
||||||
|
let tooltipContent = `<div style="padding: 12px; background: ${bgColor}; color: ${textColor}; border-radius: 6px; box-shadow: 0 2px 8px 0 hsl(0 0% 0% / ${isDarkMode ? '0.2' : '0.1'}); border: 1px solid ${borderColor};font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; font-size: 12px;">`;
|
||||||
|
|
||||||
series.forEach((s: number[], index: number) => {
|
series.forEach((s: number[], index: number) => {
|
||||||
const label = w.globals.seriesNames[index]; // Get series label
|
const label = w.globals.seriesNames[index]; // Get series label
|
||||||
const value = s[dataPointIndex]; // Get value at data point
|
const value = s[dataPointIndex]; // Get value at data point
|
||||||
tooltipContent += `<strong>${label}:</strong> ${value} Mbps<br/>`;
|
const color = w.globals.colors[index];
|
||||||
|
const formattedValue = formatter(value);
|
||||||
|
tooltipContent += `<div style="display: flex; align-items: center; gap: 8px; margin: ${index > 0 ? '6px' : '0'} 0;">
|
||||||
|
<span style="display: inline-block; width: 10px; height: 10px; background: ${color}; border-radius: 2px;"></span>
|
||||||
|
<span style="font-weight: 500;">${label}:</span>
|
||||||
|
<span style="margin-left: auto; font-weight: 600;">${formattedValue}</span>
|
||||||
|
</div>`;
|
||||||
});
|
});
|
||||||
|
|
||||||
tooltipContent += `</div>`;
|
tooltipContent += `</div>`;
|
||||||
@ -286,7 +378,7 @@ export class DeesChartArea extends DeesElement {
|
|||||||
grid: {
|
grid: {
|
||||||
xaxis: {
|
xaxis: {
|
||||||
lines: {
|
lines: {
|
||||||
show: true, // This enables the grid lines along the x-axis
|
show: false, // Hide vertical grid lines for cleaner look
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
yaxis: {
|
yaxis: {
|
||||||
@ -294,38 +386,67 @@ export class DeesChartArea extends DeesElement {
|
|||||||
show: true,
|
show: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
borderColor: '#333', // Set the color of the grid lines
|
borderColor: isDark ? 'hsl(0 0% 14.9%)' : 'hsl(0 0% 94%)', // Very subtle grid lines
|
||||||
strokeDashArray: 0, // Solid line
|
strokeDashArray: 0, // Solid line
|
||||||
row: {
|
padding: {
|
||||||
colors: [], // This can be used to alternate the shading of the horizontal rows
|
top: 10,
|
||||||
opacity: 0.1,
|
right: 20,
|
||||||
},
|
bottom: 10,
|
||||||
column: {
|
left: 20,
|
||||||
colors: [], // For vertical column bands, not needed here but available for customization
|
|
||||||
opacity: 0.1,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
fill: {
|
fill: {
|
||||||
type: 'gradient', // Gradient fill for the area
|
type: 'gradient', // Gradient fill for the area
|
||||||
gradient: {
|
gradient: {
|
||||||
shade: 'dark',
|
shade: isDark ? 'dark' : 'light',
|
||||||
type: 'vertical',
|
type: 'vertical',
|
||||||
gradientToColors: ['#9c27b0'], // Gradient color ending
|
shadeIntensity: 0.1,
|
||||||
|
opacityFrom: isDark ? 0.2 : 0.3,
|
||||||
|
opacityTo: 0,
|
||||||
stops: [0, 100],
|
stops: [0, 100],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
colors: isDark ? this.CHART_COLORS.dark : this.CHART_COLORS.light,
|
||||||
|
theme: {
|
||||||
|
mode: theme,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
this.chart = new ApexCharts(this.shadowRoot.querySelector('.chartContainer'), options);
|
this.chart = new ApexCharts(this.shadowRoot.querySelector('.chartContainer'), options);
|
||||||
await this.chart.render();
|
await this.chart.render();
|
||||||
|
|
||||||
// Give the chart a moment to fully initialize before resizing
|
// Give the chart a moment to fully initialize before resizing
|
||||||
await new Promise(resolve => setTimeout(resolve, 100));
|
await new Promise(resolve => setTimeout(resolve, 100));
|
||||||
await this.resizeChart();
|
await this.resizeChart();
|
||||||
|
|
||||||
|
// Ensure resize observer is watching the mainbox
|
||||||
|
const mainbox = this.shadowRoot.querySelector('.mainbox');
|
||||||
|
if (mainbox && this.resizeObserver) {
|
||||||
|
// Disconnect any previous observations
|
||||||
|
this.resizeObserver.disconnect();
|
||||||
|
// Start observing the mainbox
|
||||||
|
this.resizeObserver.observe(mainbox);
|
||||||
|
if (this.DEBUG_RESIZE) {
|
||||||
|
console.log('DeesChartArea - ResizeObserver attached to mainbox');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to initialize chart:', error);
|
||||||
|
// Optionally, you could set an error state here
|
||||||
|
// this.chartState = 'error';
|
||||||
|
// this.errorMessage = 'Failed to initialize chart';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async updated(changedProperties: Map<string, any>) {
|
public async updated(changedProperties: Map<string, any>) {
|
||||||
super.updated(changedProperties);
|
super.updated(changedProperties);
|
||||||
|
|
||||||
|
// Update chart theme when goBright changes
|
||||||
|
if (changedProperties.has('goBright') && this.chart) {
|
||||||
|
await this.updateChartTheme();
|
||||||
|
}
|
||||||
|
|
||||||
// Update chart if series data changes
|
// Update chart if series data changes
|
||||||
if (changedProperties.has('series') && this.chart && this.series.length > 0) {
|
if (changedProperties.has('series') && this.chart && this.series.length > 0) {
|
||||||
await this.updateSeries(this.series);
|
await this.updateSeries(this.series);
|
||||||
@ -393,6 +514,7 @@ export class DeesChartArea extends DeesElement {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
// Store the new data first
|
// Store the new data first
|
||||||
this.internalChartData = newSeries;
|
this.internalChartData = newSeries;
|
||||||
|
|
||||||
@ -429,14 +551,18 @@ export class DeesChartArea extends DeesElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.chart.updateSeries(filteredSeries, false);
|
await this.chart.updateSeries(filteredSeries, false);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.chart.updateSeries(newSeries, animate);
|
await this.chart.updateSeries(newSeries, animate);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to update chart series:', error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// New method to update just the x-axis for smooth scrolling
|
// Update just the x-axis for smooth scrolling in realtime mode
|
||||||
|
// Public for advanced usage in demos, but typically handled automatically
|
||||||
public async updateTimeWindow() {
|
public async updateTimeWindow() {
|
||||||
if (!this.chart || this.rollingWindow <= 0) {
|
if (!this.chart || this.rollingWindow <= 0) {
|
||||||
return;
|
return;
|
||||||
@ -453,8 +579,10 @@ export class DeesChartArea extends DeesElement {
|
|||||||
format: 'HH:mm:ss',
|
format: 'HH:mm:ss',
|
||||||
datetimeUTC: false,
|
datetimeUTC: false,
|
||||||
style: {
|
style: {
|
||||||
colors: '#9e9e9e',
|
colors: [!this.goBright ? 'hsl(0 0% 63.9%)' : 'hsl(0 0% 20%)'],
|
||||||
fontSize: '11px',
|
fontSize: '12px',
|
||||||
|
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif',
|
||||||
|
fontWeight: '400',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
tickAmount: 6,
|
tickAmount: 6,
|
||||||
@ -484,6 +612,11 @@ export class DeesChartArea extends DeesElement {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.DEBUG_RESIZE) {
|
||||||
|
console.log('DeesChartArea - resizeChart called');
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
const mainbox: HTMLDivElement = this.shadowRoot.querySelector('.mainbox');
|
const mainbox: HTMLDivElement = this.shadowRoot.querySelector('.mainbox');
|
||||||
const chartContainer: HTMLDivElement = this.shadowRoot.querySelector('.chartContainer');
|
const chartContainer: HTMLDivElement = this.shadowRoot.querySelector('.chartContainer');
|
||||||
|
|
||||||
@ -491,6 +624,9 @@ export class DeesChartArea extends DeesElement {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Force layout recalculation
|
||||||
|
void mainbox.offsetHeight;
|
||||||
|
|
||||||
// Get computed style of the element
|
// Get computed style of the element
|
||||||
const styleChartContainer = window.getComputedStyle(chartContainer);
|
const styleChartContainer = window.getComputedStyle(chartContainer);
|
||||||
|
|
||||||
@ -504,12 +640,33 @@ export class DeesChartArea extends DeesElement {
|
|||||||
const actualWidth = mainbox.clientWidth - paddingLeft - paddingRight;
|
const actualWidth = mainbox.clientWidth - paddingLeft - paddingRight;
|
||||||
const actualHeight = mainbox.offsetHeight - paddingTop - paddingBottom;
|
const actualHeight = mainbox.offsetHeight - paddingTop - paddingBottom;
|
||||||
|
|
||||||
|
// Validate dimensions
|
||||||
|
if (actualWidth > 0 && actualHeight > 0) {
|
||||||
|
if (this.DEBUG_RESIZE) {
|
||||||
|
console.log('DeesChartArea - Updating chart dimensions:', {
|
||||||
|
width: actualWidth,
|
||||||
|
height: actualHeight
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
await this.chart.updateOptions({
|
await this.chart.updateOptions({
|
||||||
chart: {
|
chart: {
|
||||||
width: actualWidth,
|
width: actualWidth,
|
||||||
height: actualHeight,
|
height: actualHeight,
|
||||||
},
|
},
|
||||||
});
|
}, true, false); // Redraw paths but don't animate
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to resize chart:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manually trigger a chart resize. Useful when automatic detection doesn't work.
|
||||||
|
* This is a convenience method that can be called from outside the component.
|
||||||
|
*/
|
||||||
|
public async forceResize() {
|
||||||
|
await this.resizeChart();
|
||||||
}
|
}
|
||||||
|
|
||||||
private startAutoScroll() {
|
private startAutoScroll() {
|
||||||
@ -528,4 +685,43 @@ export class DeesChartArea extends DeesElement {
|
|||||||
this.autoScrollTimer = null;
|
this.autoScrollTimer = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async updateChartTheme() {
|
||||||
|
if (!this.chart) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isDark = !this.goBright;
|
||||||
|
const theme = isDark ? 'dark' : 'light';
|
||||||
|
|
||||||
|
await this.chart.updateOptions({
|
||||||
|
theme: {
|
||||||
|
mode: theme,
|
||||||
|
},
|
||||||
|
colors: isDark ? this.CHART_COLORS.dark : this.CHART_COLORS.light,
|
||||||
|
xaxis: {
|
||||||
|
labels: {
|
||||||
|
style: {
|
||||||
|
colors: [isDark ? 'hsl(0 0% 63.9%)' : 'hsl(0 0% 20%)'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
yaxis: {
|
||||||
|
labels: {
|
||||||
|
style: {
|
||||||
|
colors: [isDark ? 'hsl(0 0% 63.9%)' : 'hsl(0 0% 20%)'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
borderColor: isDark ? 'hsl(0 0% 14.9%)' : 'hsl(0 0% 94%)',
|
||||||
|
},
|
||||||
|
fill: {
|
||||||
|
gradient: {
|
||||||
|
shade: isDark ? 'dark' : 'light',
|
||||||
|
opacityFrom: isDark ? 0.2 : 0.3,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -53,17 +53,17 @@ export class DeesChartLog extends DeesElement {
|
|||||||
cssManager.defaultStyles,
|
cssManager.defaultStyles,
|
||||||
css`
|
css`
|
||||||
:host {
|
:host {
|
||||||
font-family: 'Geist Mono', 'Consolas', 'Monaco', monospace;
|
font-family: 'SF Mono', 'Monaco', 'Consolas', 'Liberation Mono', 'Courier New', monospace;
|
||||||
color: #ccc;
|
color: ${cssManager.bdTheme('hsl(0 0% 3.9%)', 'hsl(0 0% 98%)')};
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
line-height: 1.4;
|
line-height: 1.5;
|
||||||
}
|
}
|
||||||
.mainbox {
|
.mainbox {
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 400px;
|
height: 400px;
|
||||||
background: ${cssManager.bdTheme('#f8f9fa', '#0a0a0a')};
|
background: ${cssManager.bdTheme('hsl(0 0% 100%)', 'hsl(0 0% 3.9%)')};
|
||||||
border: 1px solid ${cssManager.bdTheme('#dee2e6', '#333')};
|
border: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')};
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@ -71,9 +71,9 @@ export class DeesChartLog extends DeesElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.header {
|
.header {
|
||||||
background: ${cssManager.bdTheme('#e9ecef', '#1a1a1a')};
|
background: ${cssManager.bdTheme('hsl(0 0% 97%)', 'hsl(0 0% 7%)')};
|
||||||
padding: 8px 16px;
|
padding: 12px 16px;
|
||||||
border-bottom: 1px solid ${cssManager.bdTheme('#dee2e6', '#333')};
|
border-bottom: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')};
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@ -81,8 +81,10 @@ export class DeesChartLog extends DeesElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
font-weight: 600;
|
font-weight: 500;
|
||||||
color: ${cssManager.bdTheme('#212529', '#fff')};
|
font-size: 14px;
|
||||||
|
color: ${cssManager.bdTheme('hsl(0 0% 9%)', 'hsl(0 0% 95%)')};
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
.controls {
|
.controls {
|
||||||
@ -91,44 +93,48 @@ export class DeesChartLog extends DeesElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.control-button {
|
.control-button {
|
||||||
background: ${cssManager.bdTheme('#e9ecef', '#2a2a2a')};
|
background: ${cssManager.bdTheme('hsl(0 0% 100%)', 'hsl(0 0% 14.9%)')};
|
||||||
border: 1px solid ${cssManager.bdTheme('#ced4da', '#444')};
|
border: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')};
|
||||||
border-radius: 4px;
|
border-radius: 6px;
|
||||||
padding: 4px 8px;
|
padding: 6px 12px;
|
||||||
color: ${cssManager.bdTheme('#495057', '#ccc')};
|
color: ${cssManager.bdTheme('hsl(0 0% 45.1%)', 'hsl(0 0% 63.9%)')};
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-size: 11px;
|
font-size: 12px;
|
||||||
transition: all 0.2s;
|
font-weight: 500;
|
||||||
|
transition: all 0.15s;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
.control-button:hover {
|
.control-button:hover {
|
||||||
background: ${cssManager.bdTheme('#dee2e6', '#3a3a3a')};
|
background: ${cssManager.bdTheme('hsl(0 0% 95.1%)', 'hsl(0 0% 14.9%)')};
|
||||||
border-color: ${cssManager.bdTheme('#adb5bd', '#555')};
|
border-color: ${cssManager.bdTheme('hsl(0 0% 79.8%)', 'hsl(0 0% 20.9%)')};
|
||||||
|
color: ${cssManager.bdTheme('hsl(0 0% 15%)', 'hsl(0 0% 93.9%)')};
|
||||||
}
|
}
|
||||||
|
|
||||||
.control-button.active {
|
.control-button.active {
|
||||||
background: ${cssManager.bdTheme('#007bff', '#4a4a4a')};
|
background: ${cssManager.bdTheme('hsl(0 0% 9%)', 'hsl(0 0% 93.9%)')};
|
||||||
color: ${cssManager.bdTheme('#fff', '#fff')};
|
color: ${cssManager.bdTheme('hsl(0 0% 98%)', 'hsl(0 0% 3.9%)')};
|
||||||
}
|
}
|
||||||
|
|
||||||
.logContainer {
|
.logContainer {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
padding: 8px 16px;
|
padding: 16px;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.logEntry {
|
.logEntry {
|
||||||
margin-bottom: 2px;
|
margin-bottom: 4px;
|
||||||
display: flex;
|
display: flex;
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
word-break: break-all;
|
word-break: break-all;
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
}
|
}
|
||||||
|
|
||||||
.timestamp {
|
.timestamp {
|
||||||
color: ${cssManager.bdTheme('#6c757d', '#666')};
|
color: ${cssManager.bdTheme('hsl(0 0% 63.9%)', 'hsl(0 0% 45.1%)')};
|
||||||
margin-right: 8px;
|
margin-right: 12px;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -143,38 +149,38 @@ export class DeesChartLog extends DeesElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.level.debug {
|
.level.debug {
|
||||||
color: ${cssManager.bdTheme('#6c757d', '#999')};
|
color: ${cssManager.bdTheme('hsl(0 0% 45.1%)', 'hsl(0 0% 63.9%)')};
|
||||||
background: ${cssManager.bdTheme('rgba(108, 117, 125, 0.1)', '#333')};
|
background: ${cssManager.bdTheme('hsl(0 0% 45.1% / 0.1)', 'hsl(0 0% 63.9% / 0.1)')};
|
||||||
}
|
}
|
||||||
|
|
||||||
.level.info {
|
.level.info {
|
||||||
color: ${cssManager.bdTheme('#0066cc', '#4a9eff')};
|
color: ${cssManager.bdTheme('hsl(222.2 47.4% 51.2%)', 'hsl(217.2 91.2% 59.8%)')};
|
||||||
background: ${cssManager.bdTheme('rgba(0, 102, 204, 0.1)', 'rgba(74, 158, 255, 0.1)')};
|
background: ${cssManager.bdTheme('hsl(222.2 47.4% 51.2% / 0.1)', 'hsl(217.2 91.2% 59.8% / 0.1)')};
|
||||||
}
|
}
|
||||||
|
|
||||||
.level.warn {
|
.level.warn {
|
||||||
color: ${cssManager.bdTheme('#ff8800', '#ffb84a')};
|
color: ${cssManager.bdTheme('hsl(25 95% 53%)', 'hsl(25 95% 63%)')};
|
||||||
background: ${cssManager.bdTheme('rgba(255, 136, 0, 0.1)', 'rgba(255, 184, 74, 0.1)')};
|
background: ${cssManager.bdTheme('hsl(25 95% 53% / 0.1)', 'hsl(25 95% 63% / 0.1)')};
|
||||||
}
|
}
|
||||||
|
|
||||||
.level.error {
|
.level.error {
|
||||||
color: ${cssManager.bdTheme('#dc3545', '#ff4a4a')};
|
color: ${cssManager.bdTheme('hsl(0 84.2% 60.2%)', 'hsl(0 72.2% 50.6%)')};
|
||||||
background: ${cssManager.bdTheme('rgba(220, 53, 69, 0.1)', 'rgba(255, 74, 74, 0.1)')};
|
background: ${cssManager.bdTheme('hsl(0 84.2% 60.2% / 0.1)', 'hsl(0 72.2% 50.6% / 0.1)')};
|
||||||
}
|
}
|
||||||
|
|
||||||
.level.success {
|
.level.success {
|
||||||
color: ${cssManager.bdTheme('#28a745', '#4aff88')};
|
color: ${cssManager.bdTheme('hsl(142.1 76.2% 36.3%)', 'hsl(142.1 70.6% 45.3%)')};
|
||||||
background: ${cssManager.bdTheme('rgba(40, 167, 69, 0.1)', 'rgba(74, 255, 136, 0.1)')};
|
background: ${cssManager.bdTheme('hsl(142.1 76.2% 36.3% / 0.1)', 'hsl(142.1 70.6% 45.3% / 0.1)')};
|
||||||
}
|
}
|
||||||
|
|
||||||
.source {
|
.source {
|
||||||
color: ${cssManager.bdTheme('#6c757d', '#888')};
|
color: ${cssManager.bdTheme('hsl(0 0% 45.1%)', 'hsl(0 0% 63.9%)')};
|
||||||
margin-right: 8px;
|
margin-right: 8px;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.message {
|
.message {
|
||||||
color: ${cssManager.bdTheme('#212529', '#ddd')};
|
color: ${cssManager.bdTheme('hsl(0 0% 15%)', 'hsl(0 0% 90%)')};
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -183,7 +189,7 @@ export class DeesChartLog extends DeesElement {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
color: ${cssManager.bdTheme('#6c757d', '#666')};
|
color: ${cssManager.bdTheme('hsl(0 0% 45.1%)', 'hsl(0 0% 63.9%)')};
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -193,16 +199,16 @@ export class DeesChartLog extends DeesElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.logContainer::-webkit-scrollbar-track {
|
.logContainer::-webkit-scrollbar-track {
|
||||||
background: ${cssManager.bdTheme('#e9ecef', '#1a1a1a')};
|
background: ${cssManager.bdTheme('hsl(0 0% 95%)', 'hsl(0 0% 10%)')};
|
||||||
}
|
}
|
||||||
|
|
||||||
.logContainer::-webkit-scrollbar-thumb {
|
.logContainer::-webkit-scrollbar-thumb {
|
||||||
background: ${cssManager.bdTheme('#adb5bd', '#444')};
|
background: ${cssManager.bdTheme('hsl(0 0% 70%)', 'hsl(0 0% 30%)')};
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.logContainer::-webkit-scrollbar-thumb:hover {
|
.logContainer::-webkit-scrollbar-thumb:hover {
|
||||||
background: ${cssManager.bdTheme('#6c757d', '#555')};
|
background: ${cssManager.bdTheme('hsl(0 0% 60%)', 'hsl(0 0% 40%)')};
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
|
@ -3,47 +3,162 @@ import * as tsclass from '@tsclass/tsclass';
|
|||||||
|
|
||||||
export const demoFunc = () => html` <style>
|
export const demoFunc = () => html` <style>
|
||||||
.demo {
|
.demo {
|
||||||
background: ${cssManager.bdTheme('#eeeeeb', '#000000')};
|
background: ${cssManager.bdTheme('#f5f5f5', '#0a0a0a')};
|
||||||
display: block;
|
display: block;
|
||||||
content: '';
|
content: '';
|
||||||
padding: 40px;
|
padding: 40px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.demo-grid {
|
||||||
|
display: grid;
|
||||||
|
gap: 24px;
|
||||||
|
max-width: 800px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.demo-section {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.demo-title {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: ${cssManager.bdTheme('hsl(0 0% 45.1%)', 'hsl(0 0% 63.9%)')};
|
||||||
|
margin-bottom: 8px;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.demo-note {
|
||||||
|
font-size: 12px;
|
||||||
|
color: ${cssManager.bdTheme('hsl(0 0% 45.1%)', 'hsl(0 0% 63.9%)')};
|
||||||
|
margin-bottom: 24px;
|
||||||
|
text-align: center;
|
||||||
|
font-style: italic;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
<div class="demo">
|
<div class="demo">
|
||||||
|
<div class="demo-note">
|
||||||
|
Right-click on any detail row to copy the value, key, or key:value combination
|
||||||
|
</div>
|
||||||
|
<div class="demo-grid">
|
||||||
|
<div class="demo-section">
|
||||||
|
<div class="demo-title">Service Health Status</div>
|
||||||
<dees-dataview-statusobject
|
<dees-dataview-statusobject
|
||||||
.statusObject=${{
|
.statusObject=${{
|
||||||
id: '1',
|
id: '1',
|
||||||
name: 'Demo Item',
|
name: 'API Gateway Service',
|
||||||
combinedStatus: 'partly_ok',
|
combinedStatus: 'ok',
|
||||||
combinedStatusText: 'partly_ok',
|
combinedStatusText: 'All systems operational',
|
||||||
details: [
|
details: [
|
||||||
{
|
{
|
||||||
name: 'Detail 1',
|
name: 'Response Time',
|
||||||
value: 'Value 1',
|
value: '45ms (avg)',
|
||||||
status: 'ok',
|
status: 'ok',
|
||||||
statusText: 'OK',
|
statusText: 'Within normal range',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Detail 2',
|
name: 'Uptime',
|
||||||
value: 'Value 2',
|
value: '99.99% (30 days)',
|
||||||
status: 'partly_ok',
|
|
||||||
statusText: 'partly_ok',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Detail 3',
|
|
||||||
value: 'Value 3',
|
|
||||||
status: 'not_ok',
|
|
||||||
statusText: 'not_ok',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Detail 4',
|
|
||||||
value:
|
|
||||||
'Value 4 jhdkfjhalskdfjhfdjskalsdkfjhfdjskalskdjfhjdkslaksjdhfjdkslaskdfjhfjdkslaskdjfhjdskalskdjhfdjskalskdjfhdjskl',
|
|
||||||
status: 'ok',
|
status: 'ok',
|
||||||
statusText: 'OK',
|
statusText: 'Excellent uptime',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Active Connections',
|
||||||
|
value: '1,234 / 10,000',
|
||||||
|
status: 'ok',
|
||||||
|
statusText: 'Normal load',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'SSL Certificate',
|
||||||
|
value: 'Valid until 2024-12-31',
|
||||||
|
status: 'ok',
|
||||||
|
statusText: 'Certificate valid',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
} as tsclass.code.IStatusObject}
|
} as tsclass.code.IStatusObject}
|
||||||
>
|
>
|
||||||
</dees-dataview-statusobject>
|
</dees-dataview-statusobject>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="demo-section">
|
||||||
|
<div class="demo-title">Database Cluster Status</div>
|
||||||
|
<dees-dataview-statusobject
|
||||||
|
.statusObject=${{
|
||||||
|
id: '2',
|
||||||
|
name: 'PostgreSQL Cluster',
|
||||||
|
combinedStatus: 'partly_ok',
|
||||||
|
combinedStatusText: 'Minor issues detected',
|
||||||
|
details: [
|
||||||
|
{
|
||||||
|
name: 'Primary Node',
|
||||||
|
value: 'db-primary-01 (healthy)',
|
||||||
|
status: 'ok',
|
||||||
|
statusText: 'Operating normally',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Replica Lag',
|
||||||
|
value: '2.5 seconds',
|
||||||
|
status: 'partly_ok',
|
||||||
|
statusText: 'Slightly elevated',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Disk Usage',
|
||||||
|
value: '78% (312GB / 400GB)',
|
||||||
|
status: 'partly_ok',
|
||||||
|
statusText: 'Approaching threshold',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Connection Pool',
|
||||||
|
value: '89 / 100 connections',
|
||||||
|
status: 'ok',
|
||||||
|
statusText: 'Within limits',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
} as tsclass.code.IStatusObject}
|
||||||
|
>
|
||||||
|
</dees-dataview-statusobject>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="demo-section">
|
||||||
|
<div class="demo-title">Build Pipeline Status</div>
|
||||||
|
<dees-dataview-statusobject
|
||||||
|
.statusObject=${{
|
||||||
|
id: '3',
|
||||||
|
name: 'CI/CD Pipeline',
|
||||||
|
combinedStatus: 'not_ok',
|
||||||
|
combinedStatusText: 'Build failure',
|
||||||
|
details: [
|
||||||
|
{
|
||||||
|
name: 'Last Build',
|
||||||
|
value: 'Build #1234 - Failed',
|
||||||
|
status: 'not_ok',
|
||||||
|
statusText: 'Test failures',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Failed Tests',
|
||||||
|
value: '3 tests failed: auth.spec.ts, user.spec.ts, api.spec.ts',
|
||||||
|
status: 'not_ok',
|
||||||
|
statusText: 'Unit test failures',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Code Coverage',
|
||||||
|
value: '82.5% (target: 85%)',
|
||||||
|
status: 'partly_ok',
|
||||||
|
statusText: 'Below target',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Build Duration',
|
||||||
|
value: '12m 34s',
|
||||||
|
status: 'ok',
|
||||||
|
statusText: 'Normal duration',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
} as tsclass.code.IStatusObject}
|
||||||
|
>
|
||||||
|
</dees-dataview-statusobject>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>`;
|
</div>`;
|
||||||
|
@ -15,6 +15,7 @@ import {
|
|||||||
} from '@design.estate/dees-element';
|
} from '@design.estate/dees-element';
|
||||||
|
|
||||||
import * as tsclass from '@tsclass/tsclass';
|
import * as tsclass from '@tsclass/tsclass';
|
||||||
|
import { DeesContextmenu } from './dees-contextmenu.js';
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface HTMLElementTagNameMap {
|
interface HTMLElementTagNameMap {
|
||||||
@ -31,109 +32,128 @@ export class DeesDataviewStatusobject extends DeesElement {
|
|||||||
public static styles = [
|
public static styles = [
|
||||||
cssManager.defaultStyles,
|
cssManager.defaultStyles,
|
||||||
css`
|
css`
|
||||||
|
:host {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
.mainbox {
|
.mainbox {
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
background: ${cssManager.bdTheme('#fff', '#1b1b1b')};
|
background: ${cssManager.bdTheme('hsl(0 0% 100%)', 'hsl(0 0% 3.9%)')};
|
||||||
box-shadow: 0px 1px 3px #00000030;
|
border: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')};
|
||||||
|
box-shadow: 0 1px 3px 0 hsl(0 0% 0% / 0.1), 0 1px 2px -1px hsl(0 0% 0% / 0.1);
|
||||||
min-height: 48px;
|
min-height: 48px;
|
||||||
color: ${cssManager.bdTheme('#000', '#fff')};
|
color: ${cssManager.bdTheme('hsl(0 0% 9%)', 'hsl(0 0% 98%)')};
|
||||||
border-top: ${cssManager.bdTheme('none', '1px solid #ffffff10')};
|
|
||||||
cursor: default;
|
cursor: default;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.heading {
|
.heading {
|
||||||
display: grid;
|
display: grid;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
grid-template-columns: 40px auto 120px;
|
grid-template-columns: 48px auto 100px;
|
||||||
|
height: 56px;
|
||||||
|
padding: 0 16px;
|
||||||
|
background: ${cssManager.bdTheme('hsl(0 0% 97%)', 'hsl(0 0% 7%)')};
|
||||||
|
border-bottom: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')};
|
||||||
}
|
}
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
display: block;
|
display: block;
|
||||||
margin: 0px;
|
margin: 0px;
|
||||||
padding: 0px;
|
padding: 0px 12px;
|
||||||
height: 48px;
|
font-size: 14px;
|
||||||
text-transform: uppercase;
|
font-weight: 500;
|
||||||
font-size: 12px;
|
letter-spacing: -0.01em;
|
||||||
line-height: 48px;
|
color: ${cssManager.bdTheme('hsl(0 0% 15%)', 'hsl(0 0% 93.9%)')};
|
||||||
}
|
}
|
||||||
|
|
||||||
.statusdot {
|
.statusdot {
|
||||||
height: 8px;
|
height: 10px;
|
||||||
width: 8px;
|
width: 10px;
|
||||||
border-radius: 6px;
|
border-radius: 50%;
|
||||||
background: grey;
|
background: ${cssManager.bdTheme('hsl(0 0% 63.9%)', 'hsl(0 0% 45.1%)')};
|
||||||
margin: auto;
|
margin: auto;
|
||||||
|
box-shadow: 0 0 0 3px ${cssManager.bdTheme('hsl(0 0% 63.9% / 0.2)', 'hsl(0 0% 45.1% / 0.2)')};
|
||||||
|
transition: all 0.2s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.copyMain {
|
.copyMain {
|
||||||
font-size: 10px;
|
font-size: 12px;
|
||||||
font-weight: 600;
|
font-weight: 500;
|
||||||
text-transform: uppercase;
|
background: ${cssManager.bdTheme('hsl(0 0% 100%)', 'hsl(0 0% 14.9%)')};
|
||||||
border: 1px solid ${cssManager.bdTheme('#e0e0e0', '#444')};
|
border: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')};
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 4px;
|
padding: 6px 12px;
|
||||||
border-radius: 3px;
|
border-radius: 6px;
|
||||||
margin-right: 16px;
|
color: ${cssManager.bdTheme('hsl(0 0% 45.1%)', 'hsl(0 0% 63.9%)')};
|
||||||
color: ${cssManager.bdTheme('#333', '#ffffff80')};
|
|
||||||
user-select: none;
|
user-select: none;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.15s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.copyMain:hover {
|
.copyMain:hover {
|
||||||
background: ${cssManager.bdTheme(colors.bright.blue, colors.dark.blue)};
|
background: ${cssManager.bdTheme('hsl(0 0% 95.1%)', 'hsl(0 0% 14.9%)')};
|
||||||
border: 1px solid ${cssManager.bdTheme(colors.bright.blue, colors.dark.blue)};
|
border-color: ${cssManager.bdTheme('hsl(0 0% 79.8%)', 'hsl(0 0% 20.9%)')};
|
||||||
color: #fff;
|
color: ${cssManager.bdTheme('hsl(0 0% 15%)', 'hsl(0 0% 93.9%)')};
|
||||||
}
|
}
|
||||||
|
|
||||||
.copyMain:active {
|
.copyMain:active {
|
||||||
background: ${cssManager.bdTheme(colors.bright.blueActive, colors.dark.blueActive)};
|
background: ${cssManager.bdTheme('hsl(0 0% 91%)', 'hsl(0 0% 14.9%)')};
|
||||||
border: 1px solid ${cssManager.bdTheme(colors.bright.blueActive, colors.dark.blueActive)};
|
transform: scale(0.98);
|
||||||
color: #fff;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.statusdot.ok {
|
.statusdot.ok {
|
||||||
background: green;
|
background: ${cssManager.bdTheme('hsl(142.1 76.2% 36.3%)', 'hsl(142.1 70.6% 45.3%)')};
|
||||||
|
box-shadow: 0 0 0 3px ${cssManager.bdTheme('hsl(142.1 76.2% 36.3% / 0.2)', 'hsl(142.1 70.6% 45.3% / 0.2)')};
|
||||||
}
|
}
|
||||||
|
|
||||||
.statusdot.not_ok{
|
.statusdot.not_ok {
|
||||||
background: red;
|
background: ${cssManager.bdTheme('hsl(0 84.2% 60.2%)', 'hsl(0 72.2% 50.6%)')};
|
||||||
|
box-shadow: 0 0 0 3px ${cssManager.bdTheme('hsl(0 84.2% 60.2% / 0.2)', 'hsl(0 72.2% 50.6% / 0.2)')};
|
||||||
}
|
}
|
||||||
|
|
||||||
.statusdot.partly_ok {
|
.statusdot.partly_ok {
|
||||||
background: orange;
|
background: ${cssManager.bdTheme('hsl(25 95% 53%)', 'hsl(25 95% 63%)')};
|
||||||
|
box-shadow: 0 0 0 3px ${cssManager.bdTheme('hsl(25 95% 53% / 0.2)', 'hsl(25 95% 63% / 0.2)')};
|
||||||
}
|
}
|
||||||
|
|
||||||
.detail {
|
.detail {
|
||||||
min-height: 60px;
|
min-height: 60px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 40px auto;
|
grid-template-columns: 48px auto;
|
||||||
border-top: 1px dotted ${cssManager.bdTheme('#e0e0e0', '#282828')};
|
border-top: 1px solid ${cssManager.bdTheme('hsl(0 0% 94%)', 'hsl(0 0% 14.9%)')};
|
||||||
transition: all 0.2s;
|
transition: background-color 0.15s ease;
|
||||||
|
padding-right: 16px;
|
||||||
|
cursor: context-menu;
|
||||||
}
|
}
|
||||||
|
|
||||||
.detail:hover {
|
.detail:hover {
|
||||||
background: #ffffff05;
|
background: ${cssManager.bdTheme('hsl(0 0% 97%)', 'hsl(0 0% 7%)')};
|
||||||
}
|
}
|
||||||
|
|
||||||
.detail:active {
|
.detail:active {
|
||||||
background: #ffffff10;
|
background: ${cssManager.bdTheme('hsl(0 0% 95%)', 'hsl(0 0% 9%)')};
|
||||||
}
|
}
|
||||||
|
|
||||||
.detail .detailsText {
|
.detail .detailsText {
|
||||||
padding-top: 8px;
|
padding: 12px;
|
||||||
padding-bottom: 8px;
|
|
||||||
padding-right: 8px;
|
|
||||||
word-break: break-all;
|
word-break: break-all;
|
||||||
}
|
}
|
||||||
|
|
||||||
.detail .detailsText .label {
|
.detail .detailsText .label {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: #ffffff80
|
font-weight: 500;
|
||||||
|
color: ${cssManager.bdTheme('hsl(0 0% 45.1%)', 'hsl(0 0% 63.9%)')}
|
||||||
|
margin-bottom: 2px;
|
||||||
|
letter-spacing: -0.01em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.detail .detailsText .value {
|
.detail .detailsText .value {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
font-family: 'Intel One Mono', 'Geist Mono';
|
font-family: 'Intel One Mono', 'Geist Mono', monospace;
|
||||||
|
color: ${cssManager.bdTheme('hsl(0 0% 15%)', 'hsl(0 0% 90%)')};
|
||||||
|
line-height: 1.5;
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
@ -143,12 +163,40 @@ export class DeesDataviewStatusobject extends DeesElement {
|
|||||||
<div class="mainbox">
|
<div class="mainbox">
|
||||||
<div class="heading">
|
<div class="heading">
|
||||||
<div class="statusdot ${this.statusObject?.combinedStatus}"></div>
|
<div class="statusdot ${this.statusObject?.combinedStatus}"></div>
|
||||||
<h1>${this.statusObject?.name || 'no status object assigned'}</h1>
|
<h1>${this.statusObject?.name || 'No status object assigned'}</h1>
|
||||||
<div class="copyMain">Copy as JSON</div>
|
<div class="copyMain" @click=${this.handleCopyAsJson}>Copy JSON</div>
|
||||||
</div>
|
</div>
|
||||||
${this.statusObject?.details?.map((detailArg) => {
|
${this.statusObject?.details?.map((detailArg) => {
|
||||||
return html`
|
return html`
|
||||||
<div class="detail">
|
<div
|
||||||
|
class="detail"
|
||||||
|
@contextmenu=${(event: MouseEvent) => {
|
||||||
|
event.preventDefault();
|
||||||
|
DeesContextmenu.openContextMenuWithOptions(event, [
|
||||||
|
{
|
||||||
|
name: 'Copy Value',
|
||||||
|
iconName: 'lucideCopy',
|
||||||
|
action: async () => {
|
||||||
|
await this.copyToClipboard(detailArg.value, 'Value');
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Copy Key',
|
||||||
|
iconName: 'lucideKey',
|
||||||
|
action: async () => {
|
||||||
|
await this.copyToClipboard(detailArg.name, 'Key');
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Copy Key:Value',
|
||||||
|
iconName: 'lucideCopyPlus',
|
||||||
|
action: async () => {
|
||||||
|
await this.copyToClipboard(`${detailArg.name}: ${detailArg.value}`, 'Key:Value');
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
}}
|
||||||
|
>
|
||||||
<div class="statusdot ${detailArg.status}"></div>
|
<div class="statusdot ${detailArg.status}"></div>
|
||||||
<div class="detailsText">
|
<div class="detailsText">
|
||||||
<div class="label">${detailArg.name}</div>
|
<div class="label">${detailArg.name}</div>
|
||||||
@ -162,4 +210,42 @@ export class DeesDataviewStatusobject extends DeesElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async firstUpdated() {}
|
async firstUpdated() {}
|
||||||
|
|
||||||
|
private async copyToClipboard(text: string, type: string = 'Text') {
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.writeText(text);
|
||||||
|
console.log(`${type} copied to clipboard`);
|
||||||
|
// You could add visual feedback here if needed
|
||||||
|
} catch (err) {
|
||||||
|
console.error(`Failed to copy ${type}:`, err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async handleCopyAsJson() {
|
||||||
|
if (!this.statusObject) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.writeText(JSON.stringify(this.statusObject, null, 2));
|
||||||
|
|
||||||
|
// Show feedback
|
||||||
|
const button = this.shadowRoot.querySelector('.copyMain') as HTMLElement;
|
||||||
|
const originalText = button.textContent;
|
||||||
|
button.textContent = 'Copied!';
|
||||||
|
|
||||||
|
// Apply success styles based on theme
|
||||||
|
const isDark = !this.goBright;
|
||||||
|
button.style.background = isDark ? 'hsl(142.1 70.6% 45.3% / 0.1)' : 'hsl(142.1 76.2% 36.3% / 0.1)';
|
||||||
|
button.style.borderColor = isDark ? 'hsl(142.1 70.6% 45.3%)' : 'hsl(142.1 76.2% 36.3%)';
|
||||||
|
button.style.color = isDark ? 'hsl(142.1 70.6% 45.3%)' : 'hsl(142.1 76.2% 36.3%)';
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
button.textContent = originalText;
|
||||||
|
button.style.background = '';
|
||||||
|
button.style.borderColor = '';
|
||||||
|
button.style.color = '';
|
||||||
|
}, 1500);
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Failed to copy:', err);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { html, css } from '@design.estate/dees-element';
|
import { html, css, cssManager } from '@design.estate/dees-element';
|
||||||
import '@design.estate/dees-wcctools/demotools';
|
import '@design.estate/dees-wcctools/demotools';
|
||||||
|
import './dees-panel.js';
|
||||||
import type { DeesInputCheckbox } from './dees-input-checkbox.js';
|
import type { DeesInputCheckbox } from './dees-input-checkbox.js';
|
||||||
import './dees-button.js';
|
import './dees-button.js';
|
||||||
|
|
||||||
@ -41,62 +42,49 @@ export const demoFunc = () => html`
|
|||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.demo-section {
|
dees-panel {
|
||||||
background: #f8f9fa;
|
margin-bottom: 24px;
|
||||||
border-radius: 8px;
|
|
||||||
padding: 24px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (prefers-color-scheme: dark) {
|
dees-panel:last-child {
|
||||||
.demo-section {
|
margin-bottom: 0;
|
||||||
background: #1a1a1a;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.demo-section h3 {
|
|
||||||
margin-top: 0;
|
|
||||||
margin-bottom: 16px;
|
|
||||||
color: #0069f2;
|
|
||||||
font-size: 18px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.demo-section p {
|
|
||||||
margin-top: 0;
|
|
||||||
margin-bottom: 16px;
|
|
||||||
color: #666;
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (prefers-color-scheme: dark) {
|
|
||||||
.demo-section p {
|
|
||||||
color: #999;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.horizontal-group {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 16px;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.checkbox-group {
|
.checkbox-group {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 8px;
|
gap: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.feature-list {
|
.horizontal-checkboxes {
|
||||||
background: #f0f0f0;
|
display: flex;
|
||||||
border-radius: 4px;
|
gap: 24px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.interactive-section {
|
||||||
|
background: ${cssManager.bdTheme('hsl(210 40% 96.1%)', 'hsl(215 20.2% 16.8%)')};
|
||||||
|
border-radius: 8px;
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
margin-bottom: 16px;
|
margin-top: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (prefers-color-scheme: dark) {
|
.output-text {
|
||||||
.feature-list {
|
font-family: monospace;
|
||||||
background: #0a0a0a;
|
font-size: 13px;
|
||||||
|
color: ${cssManager.bdTheme('hsl(215.3 25% 26.7%)', 'hsl(210 40% 80%)')};
|
||||||
|
padding: 8px;
|
||||||
|
background: ${cssManager.bdTheme('hsl(210 40% 98%)', 'hsl(215 20.2% 11.8%)')};
|
||||||
|
border-radius: 4px;
|
||||||
|
min-height: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.form-section {
|
||||||
|
background: ${cssManager.bdTheme('hsl(0 0% 97%)', 'hsl(0 0% 7%)')};
|
||||||
|
border: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')};
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 20px;
|
||||||
|
margin-top: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.button-group {
|
.button-group {
|
||||||
@ -104,14 +92,26 @@ export const demoFunc = () => html`
|
|||||||
gap: 8px;
|
gap: 8px;
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.feature-list {
|
||||||
|
background: ${cssManager.bdTheme('hsl(210 40% 96.1%)', 'hsl(215 20.2% 11.8%)')};
|
||||||
|
border: 1px solid ${cssManager.bdTheme('hsl(214.3 31.8% 91.4%)', 'hsl(215 20.2% 16.8%)')};
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-title {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
color: ${cssManager.bdTheme('hsl(215.3 25% 8.8%)', 'hsl(210 40% 98%)')};
|
||||||
|
}
|
||||||
`}
|
`}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<div class="demo-container">
|
<div class="demo-container">
|
||||||
<div class="demo-section">
|
<dees-panel .title=${'Basic Checkboxes'} .subtitle=${'Simple checkbox examples with various labels'}>
|
||||||
<h3>Basic Checkboxes</h3>
|
<div class="checkbox-group">
|
||||||
<p>Standard checkbox inputs for boolean selections</p>
|
|
||||||
|
|
||||||
<dees-input-checkbox
|
<dees-input-checkbox
|
||||||
.label=${'I agree to the Terms and Conditions'}
|
.label=${'I agree to the Terms and Conditions'}
|
||||||
.value=${true}
|
.value=${true}
|
||||||
@ -126,48 +126,78 @@ export const demoFunc = () => html`
|
|||||||
|
|
||||||
<dees-input-checkbox
|
<dees-input-checkbox
|
||||||
.label=${'Enable notifications'}
|
.label=${'Enable notifications'}
|
||||||
.required=${true}
|
.value=${false}
|
||||||
|
.description=${'Receive email updates about your account'}
|
||||||
.key=${'notifications'}
|
.key=${'notifications'}
|
||||||
></dees-input-checkbox>
|
></dees-input-checkbox>
|
||||||
</div>
|
</div>
|
||||||
|
</dees-panel>
|
||||||
|
|
||||||
<div class="demo-section">
|
<dees-panel .title=${'Checkbox States'} .subtitle=${'Different checkbox states and configurations'}>
|
||||||
<h3>Horizontal Layout</h3>
|
<div class="checkbox-group">
|
||||||
<p>Checkboxes arranged horizontally for compact forms</p>
|
<dees-input-checkbox
|
||||||
|
.label=${'Default state'}
|
||||||
|
.value=${false}
|
||||||
|
></dees-input-checkbox>
|
||||||
|
|
||||||
<div class="horizontal-group">
|
<dees-input-checkbox
|
||||||
|
.label=${'Checked state'}
|
||||||
|
.value=${true}
|
||||||
|
></dees-input-checkbox>
|
||||||
|
|
||||||
|
<dees-input-checkbox
|
||||||
|
.label=${'Disabled unchecked'}
|
||||||
|
.value=${false}
|
||||||
|
.disabled=${true}
|
||||||
|
></dees-input-checkbox>
|
||||||
|
|
||||||
|
<dees-input-checkbox
|
||||||
|
.label=${'Disabled checked'}
|
||||||
|
.value=${true}
|
||||||
|
.disabled=${true}
|
||||||
|
></dees-input-checkbox>
|
||||||
|
|
||||||
|
<dees-input-checkbox
|
||||||
|
.label=${'Required checkbox'}
|
||||||
|
.required=${true}
|
||||||
|
.key=${'required'}
|
||||||
|
></dees-input-checkbox>
|
||||||
|
</div>
|
||||||
|
</dees-panel>
|
||||||
|
|
||||||
|
<dees-panel .title=${'Horizontal Layout'} .subtitle=${'Checkboxes arranged horizontally for compact forms'}>
|
||||||
|
<div class="horizontal-checkboxes">
|
||||||
<dees-input-checkbox
|
<dees-input-checkbox
|
||||||
.label=${'Option A'}
|
.label=${'Option A'}
|
||||||
|
.value=${false}
|
||||||
.layoutMode=${'horizontal'}
|
.layoutMode=${'horizontal'}
|
||||||
.key=${'optionA'}
|
.key=${'optionA'}
|
||||||
></dees-input-checkbox>
|
></dees-input-checkbox>
|
||||||
|
|
||||||
<dees-input-checkbox
|
<dees-input-checkbox
|
||||||
.label=${'Option B'}
|
.label=${'Option B'}
|
||||||
.layoutMode=${'horizontal'}
|
|
||||||
.value=${true}
|
.value=${true}
|
||||||
|
.layoutMode=${'horizontal'}
|
||||||
.key=${'optionB'}
|
.key=${'optionB'}
|
||||||
></dees-input-checkbox>
|
></dees-input-checkbox>
|
||||||
|
|
||||||
<dees-input-checkbox
|
<dees-input-checkbox
|
||||||
.label=${'Option C'}
|
.label=${'Option C'}
|
||||||
|
.value=${false}
|
||||||
.layoutMode=${'horizontal'}
|
.layoutMode=${'horizontal'}
|
||||||
.key=${'optionC'}
|
.key=${'optionC'}
|
||||||
></dees-input-checkbox>
|
></dees-input-checkbox>
|
||||||
|
|
||||||
<dees-input-checkbox
|
<dees-input-checkbox
|
||||||
.label=${'Option D'}
|
.label=${'Option D'}
|
||||||
.layoutMode=${'horizontal'}
|
|
||||||
.value=${true}
|
.value=${true}
|
||||||
|
.layoutMode=${'horizontal'}
|
||||||
.key=${'optionD'}
|
.key=${'optionD'}
|
||||||
></dees-input-checkbox>
|
></dees-input-checkbox>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</dees-panel>
|
||||||
|
|
||||||
<div class="demo-section">
|
|
||||||
<h3>Feature Selection Example</h3>
|
|
||||||
<p>Common use case for feature toggles with batch operations</p>
|
|
||||||
|
|
||||||
|
<dees-panel .title=${'Feature Selection Example'} .subtitle=${'Common use case for feature toggles with batch operations'}>
|
||||||
<div class="button-group">
|
<div class="button-group">
|
||||||
<dees-button id="select-all-btn" type="secondary">Select All</dees-button>
|
<dees-button id="select-all-btn" type="secondary">Select All</dees-button>
|
||||||
<dees-button id="clear-all-btn" type="secondary">Clear All</dees-button>
|
<dees-button id="clear-all-btn" type="secondary">Clear All</dees-button>
|
||||||
@ -206,62 +236,72 @@ export const demoFunc = () => html`
|
|||||||
></dees-input-checkbox>
|
></dees-input-checkbox>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</dees-panel>
|
||||||
|
|
||||||
<div class="demo-section">
|
<dees-panel .title=${'Privacy Settings Example'} .subtitle=${'Checkboxes in a typical form context'}>
|
||||||
<h3>States</h3>
|
<div class="form-section">
|
||||||
<p>Different checkbox states and configurations</p>
|
<h4 class="section-title">Privacy Preferences</h4>
|
||||||
|
|
||||||
<dees-input-checkbox
|
|
||||||
.label=${'Disabled Unchecked'}
|
|
||||||
.disabled=${true}
|
|
||||||
.key=${'disabled1'}
|
|
||||||
></dees-input-checkbox>
|
|
||||||
|
|
||||||
<dees-input-checkbox
|
|
||||||
.label=${'Disabled Checked'}
|
|
||||||
.disabled=${true}
|
|
||||||
.value=${true}
|
|
||||||
.key=${'disabled2'}
|
|
||||||
></dees-input-checkbox>
|
|
||||||
|
|
||||||
<dees-input-checkbox
|
|
||||||
.label=${'Required Checkbox'}
|
|
||||||
.required=${true}
|
|
||||||
.key=${'required'}
|
|
||||||
></dees-input-checkbox>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="demo-section">
|
|
||||||
<h3>Real-world Examples</h3>
|
|
||||||
<p>Common checkbox patterns in applications</p>
|
|
||||||
|
|
||||||
<div class="checkbox-group">
|
<div class="checkbox-group">
|
||||||
<dees-input-checkbox
|
<dees-input-checkbox
|
||||||
.label=${'Remember me on this device'}
|
.label=${'Share analytics data'}
|
||||||
.value=${true}
|
.value=${true}
|
||||||
.key=${'rememberMe'}
|
.description=${'Help us improve by sharing anonymous usage data'}
|
||||||
></dees-input-checkbox>
|
></dees-input-checkbox>
|
||||||
|
|
||||||
<dees-input-checkbox
|
<dees-input-checkbox
|
||||||
.label=${'Make my profile public'}
|
.label=${'Personalized recommendations'}
|
||||||
.value=${false}
|
|
||||||
.key=${'publicProfile'}
|
|
||||||
></dees-input-checkbox>
|
|
||||||
|
|
||||||
<dees-input-checkbox
|
|
||||||
.label=${'Allow others to find me by email'}
|
|
||||||
.value=${false}
|
|
||||||
.key=${'findByEmail'}
|
|
||||||
></dees-input-checkbox>
|
|
||||||
|
|
||||||
<dees-input-checkbox
|
|
||||||
.label=${'Send me product updates and announcements'}
|
|
||||||
.value=${true}
|
.value=${true}
|
||||||
.key=${'productUpdates'}
|
.description=${'Get suggestions based on your activity'}
|
||||||
|
></dees-input-checkbox>
|
||||||
|
|
||||||
|
<dees-input-checkbox
|
||||||
|
.label=${'Marketing communications'}
|
||||||
|
.value=${false}
|
||||||
|
.description=${'Receive promotional emails and special offers'}
|
||||||
|
></dees-input-checkbox>
|
||||||
|
|
||||||
|
<dees-input-checkbox
|
||||||
|
.label=${'Third-party integrations'}
|
||||||
|
.value=${false}
|
||||||
|
.description=${'Allow approved partners to access your data'}
|
||||||
></dees-input-checkbox>
|
></dees-input-checkbox>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</dees-panel>
|
||||||
|
|
||||||
|
<dees-panel .title=${'Interactive Example'} .subtitle=${'Click checkboxes to see value changes'}>
|
||||||
|
<div class="checkbox-group">
|
||||||
|
<dees-input-checkbox
|
||||||
|
.label=${'Feature toggle'}
|
||||||
|
.value=${false}
|
||||||
|
@changeSubject=${(event: CustomEvent) => {
|
||||||
|
const output = document.querySelector('#checkbox-output');
|
||||||
|
if (output && event.detail) {
|
||||||
|
const isChecked = event.detail.getValue();
|
||||||
|
output.textContent = `Feature is ${isChecked ? 'enabled' : 'disabled'}`;
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
></dees-input-checkbox>
|
||||||
|
|
||||||
|
<dees-input-checkbox
|
||||||
|
.label=${'Debug mode'}
|
||||||
|
.value=${false}
|
||||||
|
@changeSubject=${(event: CustomEvent) => {
|
||||||
|
const output = document.querySelector('#debug-output');
|
||||||
|
if (output && event.detail) {
|
||||||
|
const isChecked = event.detail.getValue();
|
||||||
|
output.textContent = `Debug mode: ${isChecked ? 'ON' : 'OFF'}`;
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
></dees-input-checkbox>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="interactive-section">
|
||||||
|
<div id="checkbox-output" class="output-text">Feature is disabled</div>
|
||||||
|
<div id="debug-output" class="output-text" style="margin-top: 8px;">Debug mode: OFF</div>
|
||||||
|
</div>
|
||||||
|
</dees-panel>
|
||||||
</div>
|
</div>
|
||||||
</dees-demowrapper>
|
</dees-demowrapper>
|
||||||
`;
|
`;
|
@ -44,120 +44,106 @@ export class DeesInputCheckbox extends DeesInputBase<DeesInputCheckbox> {
|
|||||||
:host {
|
:host {
|
||||||
position: relative;
|
position: relative;
|
||||||
cursor: default;
|
cursor: default;
|
||||||
}
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif;
|
||||||
:host(:hover) {
|
|
||||||
filter: ${cssManager.bdTheme('brightness(0.95)', 'brightness(1.1)')};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.maincontainer {
|
.maincontainer {
|
||||||
display: flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: flex-start;
|
||||||
gap: 12px;
|
gap: 8px;
|
||||||
padding: 8px 0px;
|
|
||||||
color: ${cssManager.bdTheme('#333', '#ccc')};
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
transition: all 0.2s;
|
transition: all 0.15s ease;
|
||||||
}
|
|
||||||
|
|
||||||
.maincontainer:hover {
|
|
||||||
color: ${cssManager.bdTheme('#000', '#fff')};
|
|
||||||
}
|
|
||||||
|
|
||||||
.maincontainer:hover .checkbox {
|
|
||||||
border-color: ${cssManager.bdTheme('#999', '#888')};
|
|
||||||
}
|
|
||||||
|
|
||||||
input:focus {
|
|
||||||
outline: none;
|
|
||||||
border-bottom: 1px solid #e4002b;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.checkbox {
|
.checkbox {
|
||||||
transition: all 0.1s;
|
position: relative;
|
||||||
box-sizing: border-box;
|
height: 18px;
|
||||||
border: 1px solid ${cssManager.bdTheme('#CCC', '#999')};
|
width: 18px;
|
||||||
border-radius: 2px;
|
|
||||||
height: 24px;
|
|
||||||
width: 24px;
|
|
||||||
display: inline-block;
|
|
||||||
background: ${cssManager.bdTheme('#fafafa', '#222')};
|
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
|
border-radius: 4px;
|
||||||
|
border: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')};
|
||||||
|
background: ${cssManager.bdTheme('hsl(0 0% 100%)', 'hsl(0 0% 3.9%)')};
|
||||||
|
transition: all 0.15s ease;
|
||||||
|
margin-top: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.maincontainer:hover .checkbox {
|
||||||
|
border-color: ${cssManager.bdTheme('hsl(0 0% 79.8%)', 'hsl(0 0% 20.9%)')};
|
||||||
}
|
}
|
||||||
|
|
||||||
.checkbox.selected {
|
.checkbox.selected {
|
||||||
background: #0050b9;
|
background: ${cssManager.bdTheme('hsl(222.2 47.4% 51.2%)', 'hsl(217.2 91.2% 59.8%)')};
|
||||||
border: 1px solid #0050b9;
|
border-color: ${cssManager.bdTheme('hsl(222.2 47.4% 51.2%)', 'hsl(217.2 91.2% 59.8%)')};
|
||||||
}
|
}
|
||||||
|
|
||||||
.checkbox.disabled {
|
.checkbox:focus-visible {
|
||||||
background: none;
|
outline: none;
|
||||||
border: 1px dashed ${cssManager.bdTheme('#666666', '#666666')};
|
box-shadow: 0 0 0 3px ${cssManager.bdTheme('hsl(222.2 47.4% 51.2% / 0.1)', 'hsl(217.2 91.2% 59.8% / 0.1)')};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Checkmark using Lucide icon style */
|
||||||
.checkbox .checkmark {
|
.checkbox .checkmark {
|
||||||
display: inline-block;
|
|
||||||
width: 22px;
|
|
||||||
height: 22px;
|
|
||||||
-ms-transform: rotate(45deg); /* IE 9 */
|
|
||||||
-webkit-transform: rotate(45deg); /* Chrome, Safari, Opera */
|
|
||||||
transform: rotate(45deg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.checkbox .checkmark_stem {
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 3px;
|
top: 50%;
|
||||||
height: 9px;
|
left: 50%;
|
||||||
background-color: #fff;
|
transform: translate(-50%, -50%);
|
||||||
left: 11px;
|
opacity: 0;
|
||||||
top: 6px;
|
transition: opacity 0.15s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.checkbox .checkmark_kick {
|
.checkbox.selected .checkmark {
|
||||||
position: absolute;
|
opacity: 1;
|
||||||
width: 3px;
|
|
||||||
height: 3px;
|
|
||||||
background-color: #fff;
|
|
||||||
left: 8px;
|
|
||||||
top: 12px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.checkbox.disabled .checkmark_stem, .checkbox.disabled .checkmark_kick {
|
.checkbox .checkmark svg {
|
||||||
background-color: ${cssManager.bdTheme('#333', '#fff')};
|
width: 12px;
|
||||||
}
|
height: 12px;
|
||||||
|
stroke: white;
|
||||||
img {
|
stroke-width: 3;
|
||||||
padding: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.checkbox-label {
|
|
||||||
font-size: 14px;
|
|
||||||
transition: color 0.2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.maincontainer:hover .checkbox-label {
|
|
||||||
color: ${cssManager.bdTheme('#1a1a1a', '#ffffff')};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Disabled state */
|
||||||
.maincontainer.disabled {
|
.maincontainer.disabled {
|
||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
.maincontainer.disabled:hover {
|
.checkbox.disabled {
|
||||||
color: ${cssManager.bdTheme('#333', '#ccc')};
|
background: ${cssManager.bdTheme('hsl(0 0% 95.1%)', 'hsl(0 0% 14.9%)')};
|
||||||
|
border-color: ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')};
|
||||||
}
|
}
|
||||||
|
|
||||||
.maincontainer.disabled:hover .checkbox {
|
/* Label */
|
||||||
border-color: ${cssManager.bdTheme('#ccc', '#333')};
|
.label-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 2px;
|
||||||
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.checkbox-label {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 20px;
|
||||||
|
color: ${cssManager.bdTheme('hsl(0 0% 15%)', 'hsl(0 0% 90%)')};
|
||||||
|
transition: color 0.15s ease;
|
||||||
|
letter-spacing: -0.01em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.maincontainer:hover .checkbox-label {
|
||||||
|
color: ${cssManager.bdTheme('hsl(0 0% 9%)', 'hsl(0 0% 95%)')};
|
||||||
|
}
|
||||||
|
|
||||||
|
.maincontainer.disabled:hover .checkbox-label {
|
||||||
|
color: ${cssManager.bdTheme('hsl(0 0% 15%)', 'hsl(0 0% 90%)')};
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Description */
|
||||||
.description-text {
|
.description-text {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: ${cssManager.bdTheme('#666', '#999')};
|
color: ${cssManager.bdTheme('hsl(0 0% 45.1%)', 'hsl(0 0% 63.9%)')};
|
||||||
margin-top: 4px;
|
line-height: 1.5;
|
||||||
line-height: 1.4;
|
|
||||||
padding-left: 36px;
|
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
@ -166,21 +152,26 @@ export class DeesInputCheckbox extends DeesInputBase<DeesInputCheckbox> {
|
|||||||
return html`
|
return html`
|
||||||
<div class="input-wrapper">
|
<div class="input-wrapper">
|
||||||
<div class="maincontainer ${this.disabled ? 'disabled' : ''}" @click="${this.toggleSelected}">
|
<div class="maincontainer ${this.disabled ? 'disabled' : ''}" @click="${this.toggleSelected}">
|
||||||
<div class="checkbox ${this.value ? 'selected' : ''} ${this.disabled ? 'disabled' : ''}" tabindex="0">
|
<div
|
||||||
|
class="checkbox ${this.value ? 'selected' : ''} ${this.disabled ? 'disabled' : ''}"
|
||||||
|
tabindex="${this.disabled ? '-1' : '0'}"
|
||||||
|
@keydown="${this.handleKeydown}"
|
||||||
|
>
|
||||||
${this.value
|
${this.value
|
||||||
? html`
|
? html`
|
||||||
<span class="checkmark">
|
<span class="checkmark">
|
||||||
<div class="checkmark_stem"></div>
|
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<div class="checkmark_kick"></div>
|
<path d="M20 6L9 17L4 12" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
</span>
|
</span>
|
||||||
`
|
`
|
||||||
: html``}
|
: html``}
|
||||||
</div>
|
</div>
|
||||||
|
<div class="label-container">
|
||||||
${this.label ? html`<div class="checkbox-label">${this.label}</div>` : ''}
|
${this.label ? html`<div class="checkbox-label">${this.label}</div>` : ''}
|
||||||
|
${this.description ? html`<div class="description-text">${this.description}</div>` : ''}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
${this.description ? html`
|
|
||||||
<div class="description-text">${this.description}</div>
|
|
||||||
` : ''}
|
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
@ -213,4 +204,11 @@ export class DeesInputCheckbox extends DeesInputBase<DeesInputCheckbox> {
|
|||||||
(checkboxDiv as any).focus();
|
(checkboxDiv as any).focus();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private handleKeydown(event: KeyboardEvent) {
|
||||||
|
if (event.key === ' ' || event.key === 'Enter') {
|
||||||
|
event.preventDefault();
|
||||||
|
this.toggleSelected();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
import { html, css } from '@design.estate/dees-element';
|
import { html, css } from '@design.estate/dees-element';
|
||||||
import '@design.estate/dees-wcctools/demotools';
|
import '@design.estate/dees-wcctools/demotools';
|
||||||
|
import './dees-panel.js';
|
||||||
|
import './dees-form.js';
|
||||||
|
import './dees-form-submit.js';
|
||||||
|
|
||||||
export const demoFunc = () => html`
|
export const demoFunc = () => html`
|
||||||
<dees-demowrapper>
|
<dees-demowrapper>
|
||||||
@ -14,37 +17,12 @@ export const demoFunc = () => html`
|
|||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.demo-section {
|
dees-panel {
|
||||||
background: #f8f9fa;
|
margin-bottom: 24px;
|
||||||
border-radius: 8px;
|
|
||||||
padding: 24px;
|
|
||||||
position: relative;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (prefers-color-scheme: dark) {
|
dees-panel:last-child {
|
||||||
.demo-section {
|
margin-bottom: 0;
|
||||||
background: #1a1a1a;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.demo-section h3 {
|
|
||||||
margin-top: 0;
|
|
||||||
margin-bottom: 16px;
|
|
||||||
color: #0069f2;
|
|
||||||
font-size: 18px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.demo-section p {
|
|
||||||
margin-top: 0;
|
|
||||||
margin-bottom: 16px;
|
|
||||||
color: #666;
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (prefers-color-scheme: dark) {
|
|
||||||
.demo-section p {
|
|
||||||
color: #999;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.horizontal-group {
|
.horizontal-group {
|
||||||
@ -66,10 +44,7 @@ export const demoFunc = () => html`
|
|||||||
</style>
|
</style>
|
||||||
|
|
||||||
<div class="demo-container">
|
<div class="demo-container">
|
||||||
<div class="demo-section">
|
<dees-panel .title=${'1. Basic Dropdowns'} .subtitle=${'Standard dropdown with search functionality and various options'}>
|
||||||
<h3>Basic Dropdowns</h3>
|
|
||||||
<p>Standard dropdown with search functionality and various options</p>
|
|
||||||
|
|
||||||
<dees-input-dropdown
|
<dees-input-dropdown
|
||||||
.label=${'Select Country'}
|
.label=${'Select Country'}
|
||||||
.options=${[
|
.options=${[
|
||||||
@ -94,12 +69,9 @@ export const demoFunc = () => html`
|
|||||||
{ option: 'Guest', key: 'guest' }
|
{ option: 'Guest', key: 'guest' }
|
||||||
]}
|
]}
|
||||||
></dees-input-dropdown>
|
></dees-input-dropdown>
|
||||||
</div>
|
</dees-panel>
|
||||||
|
|
||||||
<div class="demo-section">
|
|
||||||
<h3>Without Search</h3>
|
|
||||||
<p>Dropdown with search functionality disabled for simpler selection</p>
|
|
||||||
|
|
||||||
|
<dees-panel .title=${'2. Without Search'} .subtitle=${'Dropdown with search functionality disabled for simpler selection'}>
|
||||||
<dees-input-dropdown
|
<dees-input-dropdown
|
||||||
.label=${'Priority Level'}
|
.label=${'Priority Level'}
|
||||||
.enableSearch=${false}
|
.enableSearch=${false}
|
||||||
@ -110,12 +82,9 @@ export const demoFunc = () => html`
|
|||||||
]}
|
]}
|
||||||
.selectedOption=${{ option: 'Medium', key: 'medium' }}
|
.selectedOption=${{ option: 'Medium', key: 'medium' }}
|
||||||
></dees-input-dropdown>
|
></dees-input-dropdown>
|
||||||
</div>
|
</dees-panel>
|
||||||
|
|
||||||
<div class="demo-section">
|
|
||||||
<h3>Horizontal Layout</h3>
|
|
||||||
<p>Multiple dropdowns in a horizontal layout for compact forms</p>
|
|
||||||
|
|
||||||
|
<dees-panel .title=${'3. Horizontal Layout'} .subtitle=${'Multiple dropdowns in a horizontal layout for compact forms'}>
|
||||||
<div class="horizontal-group">
|
<div class="horizontal-group">
|
||||||
<dees-input-dropdown
|
<dees-input-dropdown
|
||||||
.label=${'Department'}
|
.label=${'Department'}
|
||||||
@ -150,12 +119,9 @@ export const demoFunc = () => html`
|
|||||||
]}
|
]}
|
||||||
></dees-input-dropdown>
|
></dees-input-dropdown>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</dees-panel>
|
||||||
|
|
||||||
<div class="demo-section">
|
|
||||||
<h3>States</h3>
|
|
||||||
<p>Different states and configurations</p>
|
|
||||||
|
|
||||||
|
<dees-panel .title=${'4. States'} .subtitle=${'Different states and configurations'}>
|
||||||
<dees-input-dropdown
|
<dees-input-dropdown
|
||||||
.label=${'Required Field'}
|
.label=${'Required Field'}
|
||||||
.required=${true}
|
.required=${true}
|
||||||
@ -174,16 +140,13 @@ export const demoFunc = () => html`
|
|||||||
]}
|
]}
|
||||||
.selectedOption=${{ option: 'Cannot Select', key: 'disabled' }}
|
.selectedOption=${{ option: 'Cannot Select', key: 'disabled' }}
|
||||||
></dees-input-dropdown>
|
></dees-input-dropdown>
|
||||||
</div>
|
</dees-panel>
|
||||||
|
|
||||||
<div class="spacer">
|
<div class="spacer">
|
||||||
(Spacer to test dropdown positioning)
|
(Spacer to test dropdown positioning)
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="demo-section">
|
<dees-panel .title=${'5. Bottom Positioning'} .subtitle=${'Dropdown that opens upward when near bottom of viewport'}>
|
||||||
<h3>Bottom Positioning</h3>
|
|
||||||
<p>Dropdown that opens upward when near bottom of viewport</p>
|
|
||||||
|
|
||||||
<dees-input-dropdown
|
<dees-input-dropdown
|
||||||
.label=${'Opens Upward'}
|
.label=${'Opens Upward'}
|
||||||
.options=${[
|
.options=${[
|
||||||
@ -194,7 +157,65 @@ export const demoFunc = () => html`
|
|||||||
{ option: 'Fifth Option', key: 'fifth' }
|
{ option: 'Fifth Option', key: 'fifth' }
|
||||||
]}
|
]}
|
||||||
></dees-input-dropdown>
|
></dees-input-dropdown>
|
||||||
|
</dees-panel>
|
||||||
|
|
||||||
|
<dees-panel .title=${'6. Event Handling & Payload'} .subtitle=${'Dropdown with payload data and change event handling'}>
|
||||||
|
<dees-input-dropdown
|
||||||
|
.label=${'Select Product'}
|
||||||
|
.options=${[
|
||||||
|
{ option: 'Basic Plan', key: 'basic', payload: { price: 9.99, features: ['Feature A'] } },
|
||||||
|
{ option: 'Pro Plan', key: 'pro', payload: { price: 19.99, features: ['Feature A', 'Feature B'] } },
|
||||||
|
{ option: 'Enterprise Plan', key: 'enterprise', payload: { price: 49.99, features: ['Feature A', 'Feature B', 'Feature C'] } }
|
||||||
|
]}
|
||||||
|
@change=${(e: CustomEvent) => {
|
||||||
|
const output = document.querySelector('#selection-output');
|
||||||
|
if (output && e.detail.value) {
|
||||||
|
output.innerHTML = `
|
||||||
|
<strong>Selected:</strong> ${e.detail.value.option}<br>
|
||||||
|
<strong>Key:</strong> ${e.detail.value.key}<br>
|
||||||
|
<strong>Price:</strong> $${e.detail.value.payload?.price || 'N/A'}<br>
|
||||||
|
<strong>Features:</strong> ${e.detail.value.payload?.features?.join(', ') || 'N/A'}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
></dees-input-dropdown>
|
||||||
|
|
||||||
|
<div id="selection-output" style="margin-top: 16px; padding: 12px; background: rgba(0, 105, 242, 0.1); border-radius: 4px; font-size: 14px;">
|
||||||
|
<em>Select a product to see details...</em>
|
||||||
</div>
|
</div>
|
||||||
|
</dees-panel>
|
||||||
|
|
||||||
|
<dees-panel .title=${'7. Form Integration'} .subtitle=${'Dropdown working within a form with validation'}>
|
||||||
|
<dees-form>
|
||||||
|
<dees-input-dropdown
|
||||||
|
.label=${'Project Type'}
|
||||||
|
.key=${'projectType'}
|
||||||
|
.required=${true}
|
||||||
|
.options=${[
|
||||||
|
{ option: 'Web Application', key: 'web' },
|
||||||
|
{ option: 'Mobile Application', key: 'mobile' },
|
||||||
|
{ option: 'Desktop Application', key: 'desktop' },
|
||||||
|
{ option: 'API Service', key: 'api' }
|
||||||
|
]}
|
||||||
|
></dees-input-dropdown>
|
||||||
|
|
||||||
|
<dees-input-dropdown
|
||||||
|
.label=${'Development Framework'}
|
||||||
|
.key=${'framework'}
|
||||||
|
.required=${true}
|
||||||
|
.options=${[
|
||||||
|
{ option: 'React', key: 'react', payload: { type: 'web' } },
|
||||||
|
{ option: 'Vue.js', key: 'vue', payload: { type: 'web' } },
|
||||||
|
{ option: 'Angular', key: 'angular', payload: { type: 'web' } },
|
||||||
|
{ option: 'React Native', key: 'react-native', payload: { type: 'mobile' } },
|
||||||
|
{ option: 'Flutter', key: 'flutter', payload: { type: 'mobile' } },
|
||||||
|
{ option: 'Electron', key: 'electron', payload: { type: 'desktop' } }
|
||||||
|
]}
|
||||||
|
></dees-input-dropdown>
|
||||||
|
|
||||||
|
<dees-form-submit .text=${'Create Project'}></dees-form-submit>
|
||||||
|
</dees-form>
|
||||||
|
</dees-panel>
|
||||||
</div>
|
</div>
|
||||||
</dees-demowrapper>
|
</dees-demowrapper>
|
||||||
`
|
`
|
@ -9,7 +9,6 @@ import {
|
|||||||
} from '@design.estate/dees-element';
|
} from '@design.estate/dees-element';
|
||||||
import * as domtools from '@design.estate/dees-domtools';
|
import * as domtools from '@design.estate/dees-domtools';
|
||||||
import { demoFunc } from './dees-input-dropdown.demo.js';
|
import { demoFunc } from './dees-input-dropdown.demo.js';
|
||||||
import { DeesWindowLayer } from './dees-windowlayer.js';
|
|
||||||
import { DeesInputBase } from './dees-input-base.js';
|
import { DeesInputBase } from './dees-input-base.js';
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
@ -39,13 +38,11 @@ export class DeesInputDropdown extends DeesInputBase<DeesInputDropdown> {
|
|||||||
this.selectedOption = val;
|
this.selectedOption = val;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@property({
|
@property({
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
})
|
})
|
||||||
public enableSearch: boolean = true;
|
public enableSearch: boolean = true;
|
||||||
|
|
||||||
|
|
||||||
@state()
|
@state()
|
||||||
public opensToTop: boolean = false;
|
public opensToTop: boolean = false;
|
||||||
|
|
||||||
@ -58,6 +55,9 @@ export class DeesInputDropdown extends DeesInputBase<DeesInputDropdown> {
|
|||||||
@state()
|
@state()
|
||||||
public isOpened = false;
|
public isOpened = false;
|
||||||
|
|
||||||
|
@state()
|
||||||
|
private searchValue: string = '';
|
||||||
|
|
||||||
public static styles = [
|
public static styles = [
|
||||||
...DeesInputBase.baseStyles,
|
...DeesInputBase.baseStyles,
|
||||||
cssManager.defaultStyles,
|
cssManager.defaultStyles,
|
||||||
@ -67,123 +67,201 @@ export class DeesInputDropdown extends DeesInputBase<DeesInputDropdown> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
:host {
|
:host {
|
||||||
font-family: Roboto;
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif;
|
||||||
position: relative;
|
position: relative;
|
||||||
color: ${cssManager.bdTheme('#222', '#fff')};
|
color: ${cssManager.bdTheme('hsl(0 0% 15%)', 'hsl(0 0% 90%)')};
|
||||||
}
|
}
|
||||||
|
|
||||||
.maincontainer {
|
.maincontainer {
|
||||||
display: block;
|
display: block;
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.selectedBox {
|
.selectedBox {
|
||||||
user-select: none;
|
user-select: none;
|
||||||
position: relative;
|
position: relative;
|
||||||
max-width: 420px;
|
width: 100%;
|
||||||
height: 40px;
|
height: 40px;
|
||||||
line-height: 40px;
|
line-height: 38px;
|
||||||
padding: 0px 8px;
|
padding: 0 40px 0 12px;
|
||||||
background: ${cssManager.bdTheme('#fafafa', '#222222')};
|
background: transparent;
|
||||||
box-shadow: ${cssManager.bdTheme('0px 1px 4px rgba(0,0,0,0.3)', 'none')};
|
border: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')};
|
||||||
border-radius: 3px;
|
border-radius: 6px;
|
||||||
border-top: ${cssManager.bdTheme('1px solid #CCC', '1px solid #ffffff10')};
|
transition: all 0.15s ease;
|
||||||
border-bottom: ${cssManager.bdTheme('1px solid #CCC', '1px solid #222')};
|
font-size: 14px;
|
||||||
transition: all 0.2s ease;
|
color: ${cssManager.bdTheme('hsl(0 0% 9%)', 'hsl(0 0% 95%)')};
|
||||||
font-size: 16px;
|
cursor: pointer;
|
||||||
color: ${cssManager.bdTheme('#222', '#ccc')};
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.selectedBox:hover {
|
.selectedBox:hover:not(.disabled) {
|
||||||
filter: ${cssManager.bdTheme('brightness(0.95)', 'brightness(1.1)')};
|
border-color: ${cssManager.bdTheme('hsl(0 0% 79.8%)', 'hsl(0 0% 20.9%)')};
|
||||||
}
|
}
|
||||||
|
|
||||||
.accentBottom {
|
.selectedBox:focus-visible {
|
||||||
filter: none !important;
|
outline: none;
|
||||||
|
border-color: ${cssManager.bdTheme('hsl(222.2 47.4% 51.2%)', 'hsl(217.2 91.2% 59.8%)')};
|
||||||
|
box-shadow: 0 0 0 3px ${cssManager.bdTheme('hsl(222.2 47.4% 51.2% / 0.1)', 'hsl(217.2 91.2% 59.8% / 0.1)')};
|
||||||
}
|
}
|
||||||
|
|
||||||
.accentTop {
|
.selectedBox.disabled {
|
||||||
filter: none !important;
|
background: ${cssManager.bdTheme('hsl(0 0% 95.1%)', 'hsl(0 0% 14.9%)')};
|
||||||
|
border-color: ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')};
|
||||||
|
color: ${cssManager.bdTheme('hsl(0 0% 63.9%)', 'hsl(0 0% 45.1%)')};
|
||||||
|
cursor: not-allowed;
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Dropdown arrow */
|
||||||
|
.selectedBox::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
right: 12px;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
border-left: 4px solid transparent;
|
||||||
|
border-right: 4px solid transparent;
|
||||||
|
border-top: 4px solid ${cssManager.bdTheme('hsl(0 0% 45.1%)', 'hsl(0 0% 63.9%)')};
|
||||||
|
transition: transform 0.15s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selectedBox.open::after {
|
||||||
|
transform: translateY(-50%) rotate(180deg);
|
||||||
}
|
}
|
||||||
|
|
||||||
.selectionBox {
|
.selectionBox {
|
||||||
will-change: transform;
|
will-change: transform, opacity;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
transition: all 0.2s ease;
|
transition: all 0.15s ease;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
background: ${cssManager.bdTheme('#ffffff', '#222222')};
|
transform: translateY(-8px) scale(0.98);
|
||||||
max-width: 420px;
|
background: ${cssManager.bdTheme('hsl(0 0% 100%)', 'hsl(0 0% 3.9%)')};
|
||||||
box-shadow: 0px 0px 5px rgba(0, 0, 0, 0.2);
|
border: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')};
|
||||||
|
box-shadow: 0 4px 6px -1px hsl(0 0% 0% / 0.1), 0 2px 4px -2px hsl(0 0% 0% / 0.1);
|
||||||
min-height: 40px;
|
min-height: 40px;
|
||||||
border-radius: 8px;
|
max-height: 300px;
|
||||||
padding: 4px 8px;
|
overflow: hidden;
|
||||||
|
border-radius: 6px;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
margin: 3px 0px 0px 0px;
|
margin-top: 4px;
|
||||||
border-top: ${cssManager.bdTheme('1px solid #CCC', '1px solid #ffffff10')};
|
z-index: 50;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.selectionBox.top {
|
.selectionBox.top {
|
||||||
transform: translate(0px, 4px);
|
bottom: calc(100% + 4px);
|
||||||
|
top: auto;
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
transform: translateY(8px) scale(0.98);
|
||||||
}
|
}
|
||||||
|
|
||||||
.selectionBox.bottom {
|
.selectionBox.bottom {
|
||||||
transform: translate(0px, -4px);
|
top: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.selectionBox.show {
|
.selectionBox.show {
|
||||||
pointer-events: all;
|
pointer-events: all;
|
||||||
transform: scale(1, 1) translate(0px, 0px);
|
transform: translateY(0) scale(1);
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
box-shadow: 0px 0px 8px rgba(0, 0, 0, 0.8);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Options container */
|
||||||
|
.options-container {
|
||||||
|
max-height: 250px;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Options */
|
||||||
.option {
|
.option {
|
||||||
transition: all 0.1s;
|
transition: all 0.15s ease;
|
||||||
line-height: 32px;
|
line-height: 32px;
|
||||||
padding: 0px 4px;
|
padding: 0 8px;
|
||||||
border-radius: 3px;
|
border-radius: 4px;
|
||||||
margin: 4px 0px;
|
margin: 2px 0;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 14px;
|
||||||
|
color: ${cssManager.bdTheme('hsl(0 0% 15%)', 'hsl(0 0% 90%)')};
|
||||||
}
|
}
|
||||||
|
|
||||||
.option.highlighted {
|
.option.highlighted {
|
||||||
border-left: 2px solid #0069f2;
|
background: ${cssManager.bdTheme('hsl(0 0% 95.1%)', 'hsl(0 0% 14.9%)')};
|
||||||
padding-left: 6px;
|
|
||||||
background: #ffffff20;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.option:hover {
|
.option:hover {
|
||||||
color: #fff;
|
background: ${cssManager.bdTheme('hsl(0 0% 95.1%)', 'hsl(0 0% 14.9%)')};
|
||||||
padding-left: 8px;
|
color: ${cssManager.bdTheme('hsl(0 0% 9%)', 'hsl(0 0% 95%)')};
|
||||||
background: #0069f2;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.search.top {
|
/* No options message */
|
||||||
padding-top: 4px;
|
.no-options {
|
||||||
|
padding: 8px;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 14px;
|
||||||
|
color: ${cssManager.bdTheme('hsl(0 0% 45.1%)', 'hsl(0 0% 63.9%)')};
|
||||||
|
font-style: italic;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Search */
|
||||||
|
.search {
|
||||||
|
padding: 4px;
|
||||||
|
border-bottom: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')};
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
.search.bottom {
|
.search.bottom {
|
||||||
padding-bottom: 4px;
|
border-bottom: none;
|
||||||
|
border-top: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')};
|
||||||
|
margin-bottom: 0;
|
||||||
|
margin-top: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.search input {
|
.search input {
|
||||||
display: block;
|
display: block;
|
||||||
background: none;
|
|
||||||
border: none;
|
|
||||||
height: 24px;
|
|
||||||
color: inherit;
|
|
||||||
text-align: left;
|
|
||||||
font-size: 12px;
|
|
||||||
font-weight: 600;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin: auto;
|
height: 32px;
|
||||||
|
padding: 0 8px;
|
||||||
|
background: transparent;
|
||||||
|
border: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')};
|
||||||
|
border-radius: 4px;
|
||||||
|
color: inherit;
|
||||||
|
font-size: 14px;
|
||||||
|
font-family: inherit;
|
||||||
|
outline: none;
|
||||||
|
transition: border-color 0.15s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.search.top input {
|
.search input::placeholder {
|
||||||
border-bottom: 1px dotted #333;
|
color: ${cssManager.bdTheme('hsl(0 0% 63.9%)', 'hsl(0 0% 45.1%)')};
|
||||||
}
|
|
||||||
.search.bottom input {
|
|
||||||
border-top: 1px dotted #333;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.search input:focus {
|
.search input:focus {
|
||||||
outline: none;
|
border-color: ${cssManager.bdTheme('hsl(222.2 47.4% 51.2%)', 'hsl(217.2 91.2% 59.8%)')};
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Scrollbar styling */
|
||||||
|
.options-container::-webkit-scrollbar {
|
||||||
|
width: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.options-container::-webkit-scrollbar-track {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.options-container::-webkit-scrollbar-thumb {
|
||||||
|
background: ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')};
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.options-container::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: ${cssManager.bdTheme('hsl(0 0% 79.8%)', 'hsl(0 0% 20.9%)')};
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
@ -191,61 +269,78 @@ export class DeesInputDropdown extends DeesInputBase<DeesInputDropdown> {
|
|||||||
public render(): TemplateResult {
|
public render(): TemplateResult {
|
||||||
return html`
|
return html`
|
||||||
<div class="input-wrapper">
|
<div class="input-wrapper">
|
||||||
<dees-label .label=${this.label}></dees-label>
|
<dees-label .label=${this.label} .description=${this.description} .required=${this.required}></dees-label>
|
||||||
<div class="maincontainer" @keydown="${this.isOpened ? this.handleKeyDown : undefined}">
|
<div class="maincontainer">
|
||||||
<div class="selectionBox">
|
<div
|
||||||
${this.enableSearch && !this.opensToTop
|
class="selectedBox ${this.isOpened ? 'open' : ''} ${this.disabled ? 'disabled' : ''}"
|
||||||
|
@click="${() => !this.disabled && this.toggleSelectionBox()}"
|
||||||
|
tabindex="${this.disabled ? '-1' : '0'}"
|
||||||
|
@keydown="${this.handleSelectedBoxKeydown}"
|
||||||
|
>
|
||||||
|
${this.selectedOption?.option || 'Select an option'}
|
||||||
|
</div>
|
||||||
|
<div class="selectionBox ${this.isOpened ? 'show' : ''} ${this.opensToTop ? 'top' : 'bottom'}">
|
||||||
|
${this.enableSearch
|
||||||
? html`
|
? html`
|
||||||
<div class="search top">
|
<div class="search">
|
||||||
<input type="text" placeholder="Search" @input="${this.handleSearch}" />
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="Search options..."
|
||||||
|
.value="${this.searchValue}"
|
||||||
|
@input="${this.handleSearch}"
|
||||||
|
@click="${(e: Event) => e.stopPropagation()}"
|
||||||
|
@keydown="${this.handleSearchKeydown}"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
: null}
|
: null}
|
||||||
${this.filteredOptions.map((option, index) => {
|
<div class="options-container">
|
||||||
|
${this.filteredOptions.length === 0
|
||||||
|
? html`<div class="no-options">No options found</div>`
|
||||||
|
: this.filteredOptions.map((option, index) => {
|
||||||
const isHighlighted = this.highlightedIndex === index;
|
const isHighlighted = this.highlightedIndex === index;
|
||||||
return html`
|
return html`
|
||||||
<div
|
<div
|
||||||
class="option ${isHighlighted ? 'highlighted' : ''}"
|
class="option ${isHighlighted ? 'highlighted' : ''}"
|
||||||
@click=${() => {
|
@click="${() => this.updateSelection(option)}"
|
||||||
this.updateSelection(option);
|
@mouseenter="${() => this.highlightedIndex = index}"
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
${option.option}
|
${option.option}
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
})}
|
})
|
||||||
${this.enableSearch && this.opensToTop
|
|
||||||
? html`
|
|
||||||
<div class="search bottom">
|
|
||||||
<input type="text" placeholder="Search" @input="${this.handleSearch}" />
|
|
||||||
</div>
|
|
||||||
`
|
|
||||||
: null}
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="selectedBox"
|
|
||||||
@click="${(event) => {
|
|
||||||
if (!this.isElevated) {
|
|
||||||
this.toggleSelectionBox();
|
|
||||||
} else {
|
|
||||||
this.updateSelection(this.selectedOption);
|
|
||||||
}
|
}
|
||||||
}}"
|
</div>
|
||||||
>
|
|
||||||
${this.selectedOption?.option || 'Select...'}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
firstUpdated() {
|
async connectedCallback() {
|
||||||
this.selectedOption = this.selectedOption || null;
|
super.connectedCallback();
|
||||||
this.filteredOptions = this.options; // Initialize filteredOptions
|
this.handleClickOutside = this.handleClickOutside.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async updateSelection(selectedOption) {
|
firstUpdated() {
|
||||||
|
this.selectedOption = this.selectedOption || null;
|
||||||
|
this.filteredOptions = this.options;
|
||||||
|
}
|
||||||
|
|
||||||
|
updated(changedProperties: Map<string, any>) {
|
||||||
|
super.updated(changedProperties);
|
||||||
|
|
||||||
|
if (changedProperties.has('options')) {
|
||||||
|
this.filteredOptions = this.options;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async updateSelection(selectedOption: { option: string; key: string; payload?: any }) {
|
||||||
this.selectedOption = selectedOption;
|
this.selectedOption = selectedOption;
|
||||||
|
this.isOpened = false;
|
||||||
|
this.searchValue = '';
|
||||||
|
this.filteredOptions = this.options;
|
||||||
|
this.highlightedIndex = 0;
|
||||||
|
|
||||||
this.dispatchEvent(
|
this.dispatchEvent(
|
||||||
new CustomEvent('selectedOption', {
|
new CustomEvent('selectedOption', {
|
||||||
@ -253,129 +348,105 @@ export class DeesInputDropdown extends DeesInputBase<DeesInputDropdown> {
|
|||||||
bubbles: true,
|
bubbles: true,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
if (this.isElevated) {
|
|
||||||
this.toggleSelectionBox();
|
|
||||||
}
|
|
||||||
this.changeSubject.next(this);
|
this.changeSubject.next(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
private isElevated: boolean = false;
|
private handleClickOutside = (event: MouseEvent) => {
|
||||||
private windowOverlay: DeesWindowLayer;
|
const path = event.composedPath();
|
||||||
|
if (!path.includes(this)) {
|
||||||
|
this.isOpened = false;
|
||||||
|
this.searchValue = '';
|
||||||
|
this.filteredOptions = this.options;
|
||||||
|
document.removeEventListener('click', this.handleClickOutside);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
public async toggleSelectionBox() {
|
public async toggleSelectionBox() {
|
||||||
this.isOpened = !this.isOpened;
|
this.isOpened = !this.isOpened;
|
||||||
const domtoolsInstance = await this.domtoolsPromise;
|
|
||||||
const selectedBox: HTMLElement = this.shadowRoot.querySelector('.selectedBox');
|
|
||||||
const selectionBox: HTMLElement = this.shadowRoot.querySelector('.selectionBox');
|
|
||||||
if (!this.isElevated) {
|
|
||||||
this.windowOverlay = await DeesWindowLayer.createAndShow({
|
|
||||||
blur: false,
|
|
||||||
});
|
|
||||||
const elevatedDropdown = new DeesInputDropdown();
|
|
||||||
elevatedDropdown.isElevated = true;
|
|
||||||
elevatedDropdown.label = this.label;
|
|
||||||
elevatedDropdown.enableSearch = this.enableSearch;
|
|
||||||
elevatedDropdown.required = this.required;
|
|
||||||
elevatedDropdown.disabled = this.disabled;
|
|
||||||
elevatedDropdown.style.position = 'fixed';
|
|
||||||
elevatedDropdown.style.top = this.getBoundingClientRect().top + 'px';
|
|
||||||
elevatedDropdown.style.left = this.getBoundingClientRect().left + 'px';
|
|
||||||
elevatedDropdown.style.width = this.clientWidth + 'px';
|
|
||||||
|
|
||||||
// Get z-index from registry for the elevated dropdown
|
if (this.isOpened) {
|
||||||
const dropdownZIndex = (await import('./00zindex.js')).zIndexRegistry.getNextZIndex();
|
// Check available space and set position
|
||||||
elevatedDropdown.style.zIndex = dropdownZIndex.toString();
|
const selectedBox = this.shadowRoot.querySelector('.selectedBox') as HTMLElement;
|
||||||
(await import('./00zindex.js')).zIndexRegistry.register(elevatedDropdown, dropdownZIndex);
|
const rect = selectedBox.getBoundingClientRect();
|
||||||
elevatedDropdown.options = this.options;
|
const spaceBelow = window.innerHeight - rect.bottom;
|
||||||
elevatedDropdown.selectedOption = this.selectedOption;
|
const spaceAbove = rect.top;
|
||||||
elevatedDropdown.highlightedIndex = elevatedDropdown.selectedOption ? elevatedDropdown.options.indexOf(
|
|
||||||
elevatedDropdown.options.find((option) => option.key === this.selectedOption.key)
|
|
||||||
) : -1;
|
|
||||||
console.log(elevatedDropdown.options);
|
|
||||||
console.log(elevatedDropdown.selectedOption);
|
|
||||||
console.log(elevatedDropdown.highlightedIndex);
|
|
||||||
this.windowOverlay.appendChild(elevatedDropdown);
|
|
||||||
await domtoolsInstance.convenience.smartdelay.delayFor(0);
|
|
||||||
elevatedDropdown.toggleSelectionBox();
|
|
||||||
const destroyOverlay = async () => {
|
|
||||||
(elevatedDropdown.shadowRoot.querySelector('.selectionBox') as HTMLElement).style.opacity =
|
|
||||||
'0';
|
|
||||||
elevatedDropdown.removeEventListener('selectedOption', handleSelection);
|
|
||||||
this.windowOverlay.removeEventListener('clicked', destroyOverlay);
|
|
||||||
|
|
||||||
// Unregister elevated dropdown from z-index registry
|
// Determine if we should open upwards
|
||||||
(await import('./00zindex.js')).zIndexRegistry.unregister(elevatedDropdown);
|
this.opensToTop = spaceBelow < 300 && spaceAbove > spaceBelow;
|
||||||
|
|
||||||
this.windowOverlay.destroy();
|
// Focus search input if present
|
||||||
};
|
await this.updateComplete;
|
||||||
const handleSelection = async () => {
|
const searchInput = this.shadowRoot.querySelector('.search input') as HTMLInputElement;
|
||||||
await this.updateSelection(elevatedDropdown.selectedOption);
|
if (searchInput) {
|
||||||
destroyOverlay();
|
searchInput.focus();
|
||||||
};
|
|
||||||
elevatedDropdown.addEventListener('selectedOption', handleSelection);
|
|
||||||
this.windowOverlay.addEventListener('clicked', destroyOverlay);
|
|
||||||
} else {
|
|
||||||
if (!selectionBox.classList.contains('show')) {
|
|
||||||
selectionBox.style.width = selectedBox.clientWidth + 'px';
|
|
||||||
const spaceData = selectedBox.getBoundingClientRect();
|
|
||||||
if (300 > window.innerHeight - spaceData.bottom) {
|
|
||||||
this.opensToTop = true;
|
|
||||||
selectedBox.classList.add('accentTop');
|
|
||||||
selectionBox.classList.add('top');
|
|
||||||
selectionBox.style.bottom = selectedBox.clientHeight + 2 + 'px';
|
|
||||||
} else {
|
|
||||||
selectedBox.classList.add('accentBottom');
|
|
||||||
selectionBox.classList.add('bottom');
|
|
||||||
this.opensToTop = false;
|
|
||||||
const labelOffset = this.label ? 24 : 0;
|
|
||||||
selectionBox.style.top = selectedBox.clientHeight + labelOffset + 'px';
|
|
||||||
}
|
}
|
||||||
await domtoolsInstance.convenience.smartdelay.delayFor(0);
|
|
||||||
const searchInput = selectionBox.querySelector('input');
|
|
||||||
searchInput?.focus();
|
|
||||||
|
|
||||||
// Get z-index from registry for the selection box
|
// Add click outside listener
|
||||||
const selectionBoxZIndex = (await import('./00zindex.js')).zIndexRegistry.getNextZIndex();
|
setTimeout(() => {
|
||||||
selectionBox.style.zIndex = selectionBoxZIndex.toString();
|
document.addEventListener('click', this.handleClickOutside);
|
||||||
(await import('./00zindex.js')).zIndexRegistry.register(selectionBox as HTMLElement, selectionBoxZIndex);
|
}, 0);
|
||||||
|
|
||||||
selectionBox.classList.add('show');
|
|
||||||
} else {
|
} else {
|
||||||
selectedBox.style.pointerEvents = 'none';
|
// Cleanup
|
||||||
selectionBox.classList.remove('show');
|
this.searchValue = '';
|
||||||
|
this.filteredOptions = this.options;
|
||||||
// Unregister selection box from z-index registry
|
document.removeEventListener('click', this.handleClickOutside);
|
||||||
(await import('./00zindex.js')).zIndexRegistry.unregister(selectionBox as HTMLElement);
|
|
||||||
|
|
||||||
// selectedBox.style.opacity = '0';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleSearch(event: Event): void {
|
private handleSearch(event: Event): void {
|
||||||
const searchTerm = (event.target as HTMLInputElement).value.toLowerCase();
|
const searchTerm = (event.target as HTMLInputElement).value;
|
||||||
|
this.searchValue = searchTerm;
|
||||||
|
const searchLower = searchTerm.toLowerCase();
|
||||||
this.filteredOptions = this.options.filter((option) =>
|
this.filteredOptions = this.options.filter((option) =>
|
||||||
option.option.toLowerCase().includes(searchTerm)
|
option.option.toLowerCase().includes(searchLower)
|
||||||
);
|
);
|
||||||
this.highlightedIndex = 0; // Reset highlighted index
|
this.highlightedIndex = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleKeyDown(event: KeyboardEvent): void {
|
private handleKeyDown(event: KeyboardEvent): void {
|
||||||
if (!this.isOpened) {
|
|
||||||
console.log('discarded key event. Check why this function is called.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const key = event.key;
|
const key = event.key;
|
||||||
const maxIndex = this.filteredOptions.length - 1;
|
const maxIndex = this.filteredOptions.length - 1;
|
||||||
|
|
||||||
if (key === 'ArrowDown') {
|
if (key === 'ArrowDown') {
|
||||||
|
event.preventDefault();
|
||||||
this.highlightedIndex = this.highlightedIndex + 1 > maxIndex ? 0 : this.highlightedIndex + 1;
|
this.highlightedIndex = this.highlightedIndex + 1 > maxIndex ? 0 : this.highlightedIndex + 1;
|
||||||
event.preventDefault();
|
|
||||||
} else if (key === 'ArrowUp') {
|
} else if (key === 'ArrowUp') {
|
||||||
|
event.preventDefault();
|
||||||
this.highlightedIndex = this.highlightedIndex - 1 < 0 ? maxIndex : this.highlightedIndex - 1;
|
this.highlightedIndex = this.highlightedIndex - 1 < 0 ? maxIndex : this.highlightedIndex - 1;
|
||||||
event.preventDefault();
|
|
||||||
} else if (key === 'Enter') {
|
} else if (key === 'Enter') {
|
||||||
this.updateSelection(this.filteredOptions[this.highlightedIndex]);
|
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
if (this.filteredOptions[this.highlightedIndex]) {
|
||||||
|
this.updateSelection(this.filteredOptions[this.highlightedIndex]);
|
||||||
|
}
|
||||||
|
} else if (key === 'Escape') {
|
||||||
|
event.preventDefault();
|
||||||
|
this.isOpened = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleSearchKeydown(event: KeyboardEvent): void {
|
||||||
|
if (event.key === 'ArrowDown' || event.key === 'ArrowUp' || event.key === 'Enter') {
|
||||||
|
this.handleKeyDown(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleSelectedBoxKeydown(event: KeyboardEvent) {
|
||||||
|
if (this.disabled) return;
|
||||||
|
|
||||||
|
if (event.key === 'Enter' || event.key === ' ') {
|
||||||
|
event.preventDefault();
|
||||||
|
this.toggleSelectionBox();
|
||||||
|
} else if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {
|
||||||
|
event.preventDefault();
|
||||||
|
if (!this.isOpened) {
|
||||||
|
this.toggleSelectionBox();
|
||||||
|
}
|
||||||
|
} else if (event.key === 'Escape') {
|
||||||
|
event.preventDefault();
|
||||||
|
if (this.isOpened) {
|
||||||
|
this.isOpened = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -386,4 +457,9 @@ export class DeesInputDropdown extends DeesInputBase<DeesInputDropdown> {
|
|||||||
public setValue(value: { option: string; key: string; payload?: any }): void {
|
public setValue(value: { option: string; key: string; payload?: any }): void {
|
||||||
this.selectedOption = value;
|
this.selectedOption = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async disconnectedCallback() {
|
||||||
|
await super.disconnectedCallback();
|
||||||
|
document.removeEventListener('click', this.handleClickOutside);
|
||||||
|
}
|
||||||
}
|
}
|
@ -68,7 +68,7 @@ export class DeesInputFileupload extends DeesInputBase<DeesInputFileupload> {
|
|||||||
:host {
|
:host {
|
||||||
position: relative;
|
position: relative;
|
||||||
display: block;
|
display: block;
|
||||||
color: ${cssManager.bdTheme('#333', '#ccc')};
|
color: ${cssManager.bdTheme('hsl(0 0% 15%)', 'hsl(0 0% 90%)')};
|
||||||
}
|
}
|
||||||
|
|
||||||
.hidden {
|
.hidden {
|
||||||
@ -83,16 +83,16 @@ export class DeesInputFileupload extends DeesInputBase<DeesInputFileupload> {
|
|||||||
|
|
||||||
.maincontainer {
|
.maincontainer {
|
||||||
position: relative;
|
position: relative;
|
||||||
border-radius: 8px;
|
border-radius: 6px;
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
background: ${cssManager.bdTheme('#f8f9fa', '#1a1a1a')};
|
background: ${cssManager.bdTheme('hsl(210 40% 98%)', 'hsl(215 20.2% 11.8%)')};
|
||||||
color: ${cssManager.bdTheme('#333', '#ccc')};
|
color: ${cssManager.bdTheme('hsl(0 0% 9%)', 'hsl(0 0% 95%)')};
|
||||||
border: 1px solid ${cssManager.bdTheme('#e0e0e0', '#333')};
|
border: 1px solid ${cssManager.bdTheme('hsl(215 20.2% 65.1%)', 'hsl(215 20.2% 35.1%)')};
|
||||||
transition: all 0.2s ease;
|
transition: all 0.15s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.maincontainer:hover {
|
.maincontainer:hover {
|
||||||
border-color: ${cssManager.bdTheme('#ccc', '#444')};
|
border-color: ${cssManager.bdTheme('hsl(215 20.2% 55.1%)', 'hsl(215 20.2% 45.1%)')};
|
||||||
}
|
}
|
||||||
|
|
||||||
:host([disabled]) .maincontainer {
|
:host([disabled]) .maincontainer {
|
||||||
@ -102,69 +102,69 @@ export class DeesInputFileupload extends DeesInputBase<DeesInputFileupload> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
:host([validationState="invalid"]) .maincontainer {
|
:host([validationState="invalid"]) .maincontainer {
|
||||||
border-color: #e74c3c;
|
border-color: ${cssManager.bdTheme('hsl(0 72.2% 50.6%)', 'hsl(0 62.8% 30.6%)')};
|
||||||
}
|
}
|
||||||
|
|
||||||
:host([validationState="valid"]) .maincontainer {
|
:host([validationState="valid"]) .maincontainer {
|
||||||
border-color: #27ae60;
|
border-color: ${cssManager.bdTheme('hsl(142.1 70.6% 45.3%)', 'hsl(142.1 76.2% 36.3%)')};
|
||||||
}
|
}
|
||||||
|
|
||||||
:host([validationState="warn"]) .maincontainer {
|
:host([validationState="warn"]) .maincontainer {
|
||||||
border-color: #f39c12;
|
border-color: ${cssManager.bdTheme('hsl(45.4 93.4% 47.5%)', 'hsl(45.4 93.4% 47.5%)')};
|
||||||
}
|
}
|
||||||
|
|
||||||
.maincontainer::after {
|
.maincontainer::after {
|
||||||
top: 2px;
|
top: 1px;
|
||||||
right: 2px;
|
right: 1px;
|
||||||
left: 2px;
|
left: 1px;
|
||||||
bottom: 2px;
|
bottom: 1px;
|
||||||
transform: scale3d(0.98, 0.9, 1);
|
transform: scale3d(0.98, 0.95, 1);
|
||||||
position: absolute;
|
position: absolute;
|
||||||
content: '';
|
content: '';
|
||||||
display: block;
|
display: block;
|
||||||
border: 2px dashed transparent;
|
border: 2px dashed transparent;
|
||||||
border-radius: 6px;
|
border-radius: 5px;
|
||||||
transition: all 0.3s ease;
|
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
.maincontainer.dragOver {
|
.maincontainer.dragOver {
|
||||||
border-color: ${cssManager.bdTheme('#0084ff', '#0084ff')};
|
border-color: ${cssManager.bdTheme('hsl(217.2 91.2% 59.8%)', 'hsl(213.1 93.9% 67.8%)')};
|
||||||
background: ${cssManager.bdTheme('#f0f8ff', '#001933')};
|
background: ${cssManager.bdTheme('hsl(217.2 91.2% 59.8% / 0.05)', 'hsl(213.1 93.9% 67.8% / 0.05)')};
|
||||||
}
|
}
|
||||||
|
|
||||||
.maincontainer.dragOver::after {
|
.maincontainer.dragOver::after {
|
||||||
transform: scale3d(1, 1, 1);
|
transform: scale3d(1, 1, 1);
|
||||||
border: 2px dashed ${cssManager.bdTheme('#0084ff', '#0084ff')};
|
border: 2px dashed ${cssManager.bdTheme('hsl(217.2 91.2% 59.8%)', 'hsl(213.1 93.9% 67.8%)')};
|
||||||
}
|
}
|
||||||
|
|
||||||
.uploadButton {
|
.uploadButton {
|
||||||
position: relative;
|
position: relative;
|
||||||
padding: 12px 24px;
|
padding: 10px 20px;
|
||||||
background: ${cssManager.bdTheme('#0084ff', '#0084ff')};
|
background: ${cssManager.bdTheme('hsl(0 0% 100%)', 'hsl(0 0% 7.8%)')};
|
||||||
color: white;
|
color: ${cssManager.bdTheme('hsl(0 0% 9%)', 'hsl(0 0% 95%)')};
|
||||||
|
border: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')};
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: all 0.2s ease;
|
transition: all 0.15s ease;
|
||||||
border: none;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
|
line-height: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.uploadButton:hover {
|
.uploadButton:hover {
|
||||||
background: ${cssManager.bdTheme('#0073e6', '#0073e6')};
|
background: ${cssManager.bdTheme('hsl(0 0% 95.1%)', 'hsl(0 0% 14.9%)')};
|
||||||
transform: translateY(-1px);
|
border-color: ${cssManager.bdTheme('hsl(0 0% 79.8%)', 'hsl(0 0% 20.9%)')};
|
||||||
box-shadow: 0 2px 8px rgba(0, 132, 255, 0.3);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.uploadButton:active {
|
.uploadButton:active {
|
||||||
transform: translateY(0);
|
background: ${cssManager.bdTheme('hsl(0 0% 91%)', 'hsl(0 0% 11%)')};
|
||||||
}
|
}
|
||||||
|
|
||||||
.uploadButton dees-icon {
|
.uploadButton dees-icon {
|
||||||
@ -181,21 +181,21 @@ export class DeesInputFileupload extends DeesInputBase<DeesInputFileupload> {
|
|||||||
.uploadCandidate {
|
.uploadCandidate {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 40px 1fr auto;
|
grid-template-columns: 40px 1fr auto;
|
||||||
background: ${cssManager.bdTheme('#ffffff', '#2a2a2a')};
|
background: ${cssManager.bdTheme('hsl(0 0% 100%)', 'hsl(215 20.2% 16.8%)')};
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
color: ${cssManager.bdTheme('#333', '#ccc')};
|
color: ${cssManager.bdTheme('hsl(0 0% 9%)', 'hsl(0 0% 95%)')};
|
||||||
cursor: default;
|
cursor: default;
|
||||||
transition: all 0.2s;
|
transition: all 0.15s ease;
|
||||||
border: 1px solid ${cssManager.bdTheme('#e0e0e0', '#333')};
|
border: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')};
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.uploadCandidate:hover {
|
.uploadCandidate:hover {
|
||||||
background: ${cssManager.bdTheme('#f5f5f5', '#333')};
|
background: ${cssManager.bdTheme('hsl(0 0% 95.1%)', 'hsl(215 20.2% 20.8%)')};
|
||||||
border-color: ${cssManager.bdTheme('#ccc', '#444')};
|
border-color: ${cssManager.bdTheme('hsl(0 0% 79.8%)', 'hsl(0 0% 20.9%)')};
|
||||||
}
|
}
|
||||||
|
|
||||||
.uploadCandidate .icon {
|
.uploadCandidate .icon {
|
||||||
@ -203,19 +203,19 @@ export class DeesInputFileupload extends DeesInputBase<DeesInputFileupload> {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
color: ${cssManager.bdTheme('#666', '#999')};
|
color: ${cssManager.bdTheme('hsl(215.4 16.3% 56.9%)', 'hsl(215 20.2% 55.1%)')};
|
||||||
}
|
}
|
||||||
|
|
||||||
.uploadCandidate.image-file .icon {
|
.uploadCandidate.image-file .icon {
|
||||||
color: #4CAF50;
|
color: ${cssManager.bdTheme('hsl(142.1 70.6% 45.3%)', 'hsl(142.1 76.2% 36.3%)')};
|
||||||
}
|
}
|
||||||
|
|
||||||
.uploadCandidate.pdf-file .icon {
|
.uploadCandidate.pdf-file .icon {
|
||||||
color: #f44336;
|
color: ${cssManager.bdTheme('hsl(0 72.2% 50.6%)', 'hsl(0 62.8% 30.6%)')};
|
||||||
}
|
}
|
||||||
|
|
||||||
.uploadCandidate.doc-file .icon {
|
.uploadCandidate.doc-file .icon {
|
||||||
color: #2196F3;
|
color: ${cssManager.bdTheme('hsl(217.2 91.2% 59.8%)', 'hsl(213.1 93.9% 67.8%)')};
|
||||||
}
|
}
|
||||||
|
|
||||||
.uploadCandidate .info {
|
.uploadCandidate .info {
|
||||||
@ -235,7 +235,7 @@ export class DeesInputFileupload extends DeesInputBase<DeesInputFileupload> {
|
|||||||
|
|
||||||
.uploadCandidate .filesize {
|
.uploadCandidate .filesize {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: ${cssManager.bdTheme('#666', '#999')};
|
color: ${cssManager.bdTheme('hsl(215.4 16.3% 56.9%)', 'hsl(215 20.2% 55.1%)')};
|
||||||
}
|
}
|
||||||
|
|
||||||
.uploadCandidate .actions {
|
.uploadCandidate .actions {
|
||||||
@ -254,13 +254,13 @@ export class DeesInputFileupload extends DeesInputBase<DeesInputFileupload> {
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
transition: all 0.2s;
|
transition: all 0.15s ease;
|
||||||
color: ${cssManager.bdTheme('#666', '#999')};
|
color: ${cssManager.bdTheme('hsl(215.4 16.3% 56.9%)', 'hsl(215 20.2% 55.1%)')};
|
||||||
}
|
}
|
||||||
|
|
||||||
.remove-button:hover {
|
.remove-button:hover {
|
||||||
background: ${cssManager.bdTheme('#fee', '#4a1c1c')};
|
background: ${cssManager.bdTheme('hsl(0 72.2% 50.6% / 0.1)', 'hsl(0 62.8% 30.6% / 0.1)')};
|
||||||
color: ${cssManager.bdTheme('#e74c3c', '#ff6b6b')};
|
color: ${cssManager.bdTheme('hsl(0 72.2% 50.6%)', 'hsl(0 62.8% 30.6%)')};
|
||||||
}
|
}
|
||||||
|
|
||||||
.clear-all-button {
|
.clear-all-button {
|
||||||
@ -271,36 +271,37 @@ export class DeesInputFileupload extends DeesInputBase<DeesInputFileupload> {
|
|||||||
.clear-all-button button {
|
.clear-all-button button {
|
||||||
background: none;
|
background: none;
|
||||||
border: none;
|
border: none;
|
||||||
color: ${cssManager.bdTheme('#666', '#999')};
|
color: ${cssManager.bdTheme('hsl(215.4 16.3% 56.9%)', 'hsl(215 20.2% 55.1%)')};
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
padding: 4px 8px;
|
padding: 4px 8px;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
transition: all 0.2s;
|
transition: all 0.15s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.clear-all-button button:hover {
|
.clear-all-button button:hover {
|
||||||
background: ${cssManager.bdTheme('#fee', '#4a1c1c')};
|
background: ${cssManager.bdTheme('hsl(0 72.2% 50.6% / 0.1)', 'hsl(0 62.8% 30.6% / 0.1)')};
|
||||||
color: ${cssManager.bdTheme('#e74c3c', '#ff6b6b')};
|
color: ${cssManager.bdTheme('hsl(0 72.2% 50.6%)', 'hsl(0 62.8% 30.6%)')};
|
||||||
}
|
}
|
||||||
|
|
||||||
.validation-message {
|
.validation-message {
|
||||||
font-size: 12px;
|
font-size: 13px;
|
||||||
margin-top: 4px;
|
margin-top: 6px;
|
||||||
color: #e74c3c;
|
color: ${cssManager.bdTheme('hsl(0 72.2% 50.6%)', 'hsl(0 62.8% 30.6%)')};
|
||||||
|
line-height: 1.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
.drop-hint {
|
.drop-hint {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 40px 20px;
|
padding: 40px 20px;
|
||||||
color: ${cssManager.bdTheme('#999', '#666')};
|
color: ${cssManager.bdTheme('hsl(215.4 16.3% 56.9%)', 'hsl(215 20.2% 55.1%)')};
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.drop-hint dees-icon {
|
.drop-hint dees-icon {
|
||||||
font-size: 48px;
|
font-size: 48px;
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
opacity: 0.3;
|
opacity: 0.2;
|
||||||
}
|
}
|
||||||
|
|
||||||
.image-preview {
|
.image-preview {
|
||||||
@ -311,10 +312,10 @@ export class DeesInputFileupload extends DeesInputBase<DeesInputFileupload> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.description-text {
|
.description-text {
|
||||||
font-size: 12px;
|
font-size: 13px;
|
||||||
color: ${cssManager.bdTheme('#666', '#999')};
|
color: ${cssManager.bdTheme('hsl(215.4 16.3% 56.9%)', 'hsl(215 20.2% 55.1%)')};
|
||||||
margin-top: 4px;
|
margin-top: 6px;
|
||||||
line-height: 1.4;
|
line-height: 1.5;
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
|
@ -10,7 +10,7 @@ import { DeesInputBase } from './dees-input-base.js';
|
|||||||
|
|
||||||
import * as colors from './00colors.js'
|
import * as colors from './00colors.js'
|
||||||
|
|
||||||
const { demoFunc } = await import('./dees-input-multitoggle.demo.js');
|
import { demoFunc } from './dees-input-multitoggle.demo.js';
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface HTMLElementTagNameMap {
|
interface HTMLElementTagNameMap {
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { html, css, cssManager } from '@design.estate/dees-element';
|
import { html, css, cssManager } from '@design.estate/dees-element';
|
||||||
|
import './dees-shopping-productcard.js';
|
||||||
|
|
||||||
export const demoFunc = () => html`
|
export const demoFunc = () => html`
|
||||||
<dees-demowrapper>
|
<dees-demowrapper>
|
||||||
@ -15,25 +16,44 @@ export const demoFunc = () => html`
|
|||||||
|
|
||||||
.shopping-grid {
|
.shopping-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
|
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
||||||
gap: 16px;
|
gap: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.product-card {
|
.cart-summary {
|
||||||
padding: 16px;
|
margin-top: 24px;
|
||||||
background: ${cssManager.bdTheme('#fff', '#2a2a2a')};
|
padding: 20px;
|
||||||
border-radius: 4px;
|
background: ${cssManager.bdTheme('hsl(210 40% 96.1%)', 'hsl(215 20.2% 16.8%)')};
|
||||||
box-shadow: 0 2px 4px ${cssManager.bdTheme('rgba(0,0,0,0.1)', 'rgba(0,0,0,0.3)')};
|
border: 1px solid ${cssManager.bdTheme('hsl(214.3 31.8% 91.4%)', 'hsl(215 20.2% 21.8%)')};
|
||||||
|
border-radius: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.product-name {
|
.cart-summary-title {
|
||||||
|
font-size: 18px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
margin-bottom: 8px;
|
margin-bottom: 16px;
|
||||||
|
color: ${cssManager.bdTheme('hsl(0 0% 9%)', 'hsl(0 0% 95%)')};
|
||||||
}
|
}
|
||||||
|
|
||||||
.product-price {
|
.cart-item {
|
||||||
color: #1976d2;
|
display: flex;
|
||||||
margin-bottom: 16px;
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 8px 0;
|
||||||
|
font-size: 14px;
|
||||||
|
color: ${cssManager.bdTheme('hsl(215.3 25% 26.7%)', 'hsl(217.9 10.6% 74.9%)')};
|
||||||
|
}
|
||||||
|
|
||||||
|
.cart-total {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding-top: 16px;
|
||||||
|
margin-top: 16px;
|
||||||
|
border-top: 2px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')};
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: ${cssManager.bdTheme('hsl(0 0% 9%)', 'hsl(0 0% 95%)')};
|
||||||
}
|
}
|
||||||
`}
|
`}
|
||||||
</style>
|
</style>
|
||||||
@ -53,36 +73,97 @@ export const demoFunc = () => html`
|
|||||||
></dees-input-quantityselector>
|
></dees-input-quantityselector>
|
||||||
</dees-panel>
|
</dees-panel>
|
||||||
|
|
||||||
<dees-panel .title=${'Shopping Cart'} .subtitle=${'Product cards with quantity selectors'}>
|
<dees-panel .title=${'Shopping Cart'} .subtitle=${'Modern e-commerce product cards with interactive quantity selectors'} .runAfterRender=${async (elementArg: HTMLElement) => {
|
||||||
|
const updateCartSummary = () => {
|
||||||
|
const card1 = elementArg.querySelector('#headphones-qty') as any;
|
||||||
|
const card2 = elementArg.querySelector('#mouse-qty') as any;
|
||||||
|
const card3 = elementArg.querySelector('#keyboard-qty') as any;
|
||||||
|
|
||||||
|
const qty1 = card1?.quantity || 0;
|
||||||
|
const qty2 = card2?.quantity || 0;
|
||||||
|
const qty3 = card3?.quantity || 0;
|
||||||
|
|
||||||
|
const price1 = 349.99 * qty1;
|
||||||
|
const price2 = 99.99 * qty2;
|
||||||
|
const price3 = 79.99 * qty3;
|
||||||
|
const total = price1 + price2 + price3;
|
||||||
|
|
||||||
|
const summary = elementArg.querySelector('#cart-summary-content');
|
||||||
|
if (summary) {
|
||||||
|
summary.innerHTML = `
|
||||||
|
${qty1 > 0 ? `<div class="cart-item">
|
||||||
|
<span>Sony WH-1000XM5 (${qty1})</span>
|
||||||
|
<span>$${price1.toFixed(2)}</span>
|
||||||
|
</div>` : ''}
|
||||||
|
${qty2 > 0 ? `<div class="cart-item">
|
||||||
|
<span>Logitech MX Master 3S (${qty2})</span>
|
||||||
|
<span>$${price2.toFixed(2)}</span>
|
||||||
|
</div>` : ''}
|
||||||
|
${qty3 > 0 ? `<div class="cart-item">
|
||||||
|
<span>Keychron K2 (${qty3})</span>
|
||||||
|
<span>$${price3.toFixed(2)}</span>
|
||||||
|
</div>` : ''}
|
||||||
|
${total === 0 ? '<div class="cart-item" style="text-align: center; color: #999;">Your cart is empty</div>' : ''}
|
||||||
|
<div class="cart-total">
|
||||||
|
<span>Total</span>
|
||||||
|
<span>$${total.toFixed(2)}</span>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Initial update
|
||||||
|
setTimeout(updateCartSummary, 100);
|
||||||
|
|
||||||
|
// Set up listeners
|
||||||
|
elementArg.querySelectorAll('dees-shopping-productcard').forEach(card => {
|
||||||
|
card.addEventListener('quantityChange', updateCartSummary);
|
||||||
|
});
|
||||||
|
}}>
|
||||||
<div class="shopping-grid">
|
<div class="shopping-grid">
|
||||||
<div class="product-card">
|
<dees-shopping-productcard
|
||||||
<div class="product-name">Premium Headphones</div>
|
id="headphones-qty"
|
||||||
<div class="product-price">$199.99</div>
|
.productData=${{
|
||||||
<dees-input-quantityselector
|
name: 'Sony WH-1000XM5 Wireless Headphones',
|
||||||
.label=${'Quantity'}
|
category: 'Audio',
|
||||||
.layoutMode=${'horizontal'}
|
description: 'Industry-leading noise canceling with Auto NC Optimizer',
|
||||||
.value=${1}
|
price: 349.99,
|
||||||
></dees-input-quantityselector>
|
originalPrice: 399.99,
|
||||||
|
iconName: 'lucide:headphones'
|
||||||
|
}}
|
||||||
|
.quantity=${1}
|
||||||
|
></dees-shopping-productcard>
|
||||||
|
|
||||||
|
<dees-shopping-productcard
|
||||||
|
id="mouse-qty"
|
||||||
|
.productData=${{
|
||||||
|
name: 'Logitech MX Master 3S',
|
||||||
|
category: 'Accessories',
|
||||||
|
description: 'Performance wireless mouse with ultra-fast scrolling',
|
||||||
|
price: 99.99,
|
||||||
|
iconName: 'lucide:mouse-pointer'
|
||||||
|
}}
|
||||||
|
.quantity=${2}
|
||||||
|
></dees-shopping-productcard>
|
||||||
|
|
||||||
|
<dees-shopping-productcard
|
||||||
|
id="keyboard-qty"
|
||||||
|
.productData=${{
|
||||||
|
name: 'Keychron K2 Wireless Mechanical Keyboard',
|
||||||
|
category: 'Keyboards',
|
||||||
|
description: 'Compact 75% layout with hot-swappable switches',
|
||||||
|
price: 79.99,
|
||||||
|
originalPrice: 94.99,
|
||||||
|
iconName: 'lucide:keyboard'
|
||||||
|
}}
|
||||||
|
.quantity=${1}
|
||||||
|
></dees-shopping-productcard>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="product-card">
|
<div class="cart-summary">
|
||||||
<div class="product-name">Wireless Mouse</div>
|
<h3 class="cart-summary-title">Order Summary</h3>
|
||||||
<div class="product-price">$49.99</div>
|
<div id="cart-summary-content">
|
||||||
<dees-input-quantityselector
|
<!-- Content will be dynamically updated -->
|
||||||
.label=${'Quantity'}
|
|
||||||
.layoutMode=${'horizontal'}
|
|
||||||
.value=${2}
|
|
||||||
></dees-input-quantityselector>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="product-card">
|
|
||||||
<div class="product-name">USB-C Cable</div>
|
|
||||||
<div class="product-price">$19.99</div>
|
|
||||||
<dees-input-quantityselector
|
|
||||||
.label=${'Quantity'}
|
|
||||||
.layoutMode=${'horizontal'}
|
|
||||||
.value=${1}
|
|
||||||
></dees-input-quantityselector>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</dees-panel>
|
</dees-panel>
|
||||||
|
@ -32,48 +32,91 @@ export class DeesInputQuantitySelector extends DeesInputBase<DeesInputQuantitySe
|
|||||||
}
|
}
|
||||||
|
|
||||||
.quantity-container {
|
.quantity-container {
|
||||||
transition: all 0.1s;
|
transition: all 0.15s ease;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
display: grid;
|
display: inline-flex;
|
||||||
grid-template-columns: 33% 34% 33%;
|
align-items: center;
|
||||||
text-align: center;
|
background: transparent;
|
||||||
background: ${cssManager.bdTheme('#fafafa', '#222222')};
|
height: 40px;
|
||||||
line-height: 40px;
|
padding: 0;
|
||||||
padding: 0px;
|
min-width: 120px;
|
||||||
min-width: 110px;
|
color: ${cssManager.bdTheme('hsl(0 0% 9%)', 'hsl(0 0% 95%)')};
|
||||||
color: ${cssManager.bdTheme('#666', '#CCC')};
|
border: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')};
|
||||||
border: 1px solid ${cssManager.bdTheme('#CCC', '#444')};
|
border-radius: 6px;
|
||||||
border-radius: 4px;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.quantity-container.disabled {
|
.quantity-container.disabled {
|
||||||
|
background: ${cssManager.bdTheme('hsl(0 0% 95.1%)', 'hsl(0 0% 14.9%)')};
|
||||||
|
border-color: ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')};
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.quantity-container:hover {
|
.quantity-container:hover:not(.disabled) {
|
||||||
color: ${cssManager.bdTheme('#333', '#fff')};
|
border-color: ${cssManager.bdTheme('hsl(0 0% 79.8%)', 'hsl(0 0% 20.9%)')};
|
||||||
border-color: ${cssManager.bdTheme('#999', '#666')};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.minus {
|
.quantity-container:focus-within {
|
||||||
padding-left: 5px;
|
border-color: ${cssManager.bdTheme('hsl(222.2 47.4% 51.2%)', 'hsl(217.2 91.2% 59.8%)')};
|
||||||
}
|
box-shadow: 0 0 0 3px ${cssManager.bdTheme('hsl(222.2 47.4% 51.2% / 0.1)', 'hsl(217.2 91.2% 59.8% / 0.1)')};
|
||||||
|
|
||||||
.plus {
|
|
||||||
padding-right: 5px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.selector {
|
.selector {
|
||||||
text-align: center;
|
flex: 0 0 40px;
|
||||||
font-size: 20px;
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 500;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.15s ease;
|
||||||
|
color: ${cssManager.bdTheme('hsl(215.4 16.3% 56.9%)', 'hsl(215 20.2% 55.1%)')};
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.selector:hover {
|
.selector:hover {
|
||||||
|
background: ${cssManager.bdTheme('hsl(0 0% 95.1%)', 'hsl(0 0% 14.9%)')};
|
||||||
|
color: ${cssManager.bdTheme('hsl(0 0% 9%)', 'hsl(0 0% 95%)')};
|
||||||
|
}
|
||||||
|
|
||||||
|
.selector:active {
|
||||||
|
background: ${cssManager.bdTheme('hsl(0 0% 91%)', 'hsl(0 0% 11%)')};
|
||||||
|
}
|
||||||
|
|
||||||
|
.selector.minus {
|
||||||
|
border-right: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')};
|
||||||
|
}
|
||||||
|
|
||||||
|
.selector.plus {
|
||||||
|
border-left: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')};
|
||||||
}
|
}
|
||||||
|
|
||||||
.quantity {
|
.quantity {
|
||||||
|
flex: 1;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
font-weight: 500;
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
letter-spacing: -0.006em;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Keyboard navigation focus styles */
|
||||||
|
.selector:focus {
|
||||||
|
outline: none;
|
||||||
|
background: ${cssManager.bdTheme('hsl(210 40% 96.1%)', 'hsl(215 20.2% 16.8%)')};
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Min value state */
|
||||||
|
.quantity-container[data-min="true"] .selector.minus {
|
||||||
|
opacity: 0.3;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quantity-container[data-min="true"] .selector.minus:hover {
|
||||||
|
background: transparent;
|
||||||
|
color: ${cssManager.bdTheme('hsl(215.4 16.3% 56.9%)', 'hsl(215 20.2% 55.1%)')};
|
||||||
}
|
}
|
||||||
|
|
||||||
`,
|
`,
|
||||||
@ -82,11 +125,38 @@ export class DeesInputQuantitySelector extends DeesInputBase<DeesInputQuantitySe
|
|||||||
public render(): TemplateResult {
|
public render(): TemplateResult {
|
||||||
return html`
|
return html`
|
||||||
<div class="input-wrapper">
|
<div class="input-wrapper">
|
||||||
<dees-label .label=${this.label}></dees-label>
|
${this.label ? html`<dees-label .label=${this.label} .description=${this.description} .required=${this.required}></dees-label>` : ''}
|
||||||
<div class="quantity-container ${this.disabled ? 'disabled' : ''}">
|
<div
|
||||||
<div class="selector minus" @click="${() => {this.decrease();}}">-</div>
|
class="quantity-container ${this.disabled ? 'disabled' : ''}"
|
||||||
<div class="quantity">${this.value}</div>
|
data-min="${this.value <= 0}"
|
||||||
<div class="selector plus" @click="${() => {this.increase();}}">+</div>
|
>
|
||||||
|
<div
|
||||||
|
class="selector minus"
|
||||||
|
@click="${() => {this.decrease();}}"
|
||||||
|
tabindex="${this.disabled ? '-1' : '0'}"
|
||||||
|
@keydown="${(e: KeyboardEvent) => {
|
||||||
|
if (e.key === 'Enter' || e.key === ' ') {
|
||||||
|
e.preventDefault();
|
||||||
|
this.decrease();
|
||||||
|
}
|
||||||
|
}}"
|
||||||
|
role="button"
|
||||||
|
aria-label="Decrease quantity"
|
||||||
|
>−</div>
|
||||||
|
<div class="quantity" aria-live="polite" aria-atomic="true">${this.value}</div>
|
||||||
|
<div
|
||||||
|
class="selector plus"
|
||||||
|
@click="${() => {this.increase();}}"
|
||||||
|
tabindex="${this.disabled ? '-1' : '0'}"
|
||||||
|
@keydown="${(e: KeyboardEvent) => {
|
||||||
|
if (e.key === 'Enter' || e.key === ' ') {
|
||||||
|
e.preventDefault();
|
||||||
|
this.increase();
|
||||||
|
}
|
||||||
|
}}"
|
||||||
|
role="button"
|
||||||
|
aria-label="Increase quantity"
|
||||||
|
>+</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
@ -69,80 +69,97 @@ export class DeesInputRadiogroup extends DeesInputBase<string | object> {
|
|||||||
:host {
|
:host {
|
||||||
display: block;
|
display: block;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
.maincontainer {
|
.maincontainer {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 8px;
|
gap: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.maincontainer.horizontal {
|
.maincontainer.horizontal {
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
gap: 16px;
|
gap: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.radio-option {
|
.radio-option {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 12px;
|
gap: 10px;
|
||||||
padding: 8px 0;
|
padding: 6px 0;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: all 0.2s ease;
|
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
user-select: none;
|
user-select: none;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
border-radius: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.maincontainer.horizontal .radio-option {
|
.maincontainer.horizontal .radio-option {
|
||||||
padding: 8px 16px 8px 0;
|
padding: 6px 20px 6px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.radio-option:hover .radio-circle {
|
.radio-option:hover .radio-circle {
|
||||||
border-color: ${cssManager.bdTheme('#0050b9', '#0084ff')};
|
border-color: ${cssManager.bdTheme('hsl(215 20.2% 65.1%)', 'hsl(215 20.2% 35.1%)')};
|
||||||
|
background: ${cssManager.bdTheme('hsl(210 40% 96.1%)', 'hsl(215 20.2% 11.8%)')};
|
||||||
}
|
}
|
||||||
|
|
||||||
.radio-option:hover .radio-label {
|
.radio-option:hover .radio-label {
|
||||||
color: ${cssManager.bdTheme('#1a1a1a', '#ffffff')};
|
color: ${cssManager.bdTheme('hsl(215.3 25% 8.8%)', 'hsl(210 40% 98%)')};
|
||||||
}
|
}
|
||||||
|
|
||||||
.radio-circle {
|
.radio-circle {
|
||||||
width: 20px;
|
width: 20px;
|
||||||
height: 20px;
|
height: 20px;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
border: 2px solid ${cssManager.bdTheme('#999', '#666')};
|
border: 2px solid ${cssManager.bdTheme('hsl(215 20.2% 65.1%)', 'hsl(215 20.2% 35.1%)')};
|
||||||
background: ${cssManager.bdTheme('#fff', '#1a1a1a')};
|
background: ${cssManager.bdTheme('hsl(0 0% 100%)', 'hsl(215 30% 6.8%)')};
|
||||||
transition: all 0.2s ease;
|
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
position: relative;
|
position: relative;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.radio-option.selected .radio-circle {
|
.radio-option.selected .radio-circle {
|
||||||
border-color: ${cssManager.bdTheme('#0050b9', '#0084ff')};
|
border-color: ${cssManager.bdTheme('hsl(217.2 91.2% 59.8%)', 'hsl(213.1 93.9% 67.8%)')};
|
||||||
background: ${cssManager.bdTheme('#0050b9', '#0084ff')};
|
background: ${cssManager.bdTheme('hsl(217.2 91.2% 59.8%)', 'hsl(213.1 93.9% 67.8%)')};
|
||||||
}
|
}
|
||||||
|
|
||||||
.radio-option.selected .radio-circle::after {
|
.radio-option.selected .radio-circle::after {
|
||||||
content: '';
|
content: '';
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 50%;
|
|
||||||
left: 50%;
|
|
||||||
transform: translate(-50%, -50%);
|
|
||||||
width: 8px;
|
width: 8px;
|
||||||
height: 8px;
|
height: 8px;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
background: white;
|
background: ${cssManager.bdTheme('hsl(0 0% 100%)', 'hsl(215 30% 6.8%)')};
|
||||||
|
transform: scale(0);
|
||||||
|
transition: transform 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.radio-option.selected .radio-circle::after {
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.radio-circle:focus-visible {
|
||||||
|
outline: none;
|
||||||
|
box-shadow: 0 0 0 2px ${cssManager.bdTheme('hsl(0 0% 100%)', 'hsl(215 30% 3.9%)')},
|
||||||
|
0 0 0 4px ${cssManager.bdTheme('hsl(217.2 91.2% 59.8%)', 'hsl(213.1 93.9% 67.8%)')};
|
||||||
}
|
}
|
||||||
|
|
||||||
.radio-label {
|
.radio-label {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
color: ${cssManager.bdTheme('#666', '#999')};
|
font-weight: 500;
|
||||||
transition: color 0.2s ease;
|
color: ${cssManager.bdTheme('hsl(215.3 25% 26.7%)', 'hsl(217.9 10.6% 74.9%)')};
|
||||||
|
transition: color 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
letter-spacing: -0.006em;
|
||||||
|
line-height: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.radio-option.selected .radio-label {
|
.radio-option.selected .radio-label {
|
||||||
color: ${cssManager.bdTheme('#1a1a1a', '#ffffff')};
|
color: ${cssManager.bdTheme('hsl(215.3 25% 8.8%)', 'hsl(210 40% 98%)')};
|
||||||
font-weight: 500;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
:host([disabled]) .radio-option {
|
:host([disabled]) .radio-option {
|
||||||
@ -151,40 +168,49 @@ export class DeesInputRadiogroup extends DeesInputBase<string | object> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
:host([disabled]) .radio-option:hover .radio-circle {
|
:host([disabled]) .radio-option:hover .radio-circle {
|
||||||
border-color: ${cssManager.bdTheme('#999', '#666')};
|
border-color: ${cssManager.bdTheme('hsl(215 20.2% 65.1%)', 'hsl(215 20.2% 35.1%)')};
|
||||||
|
background: ${cssManager.bdTheme('hsl(0 0% 100%)', 'hsl(215 30% 6.8%)')};
|
||||||
}
|
}
|
||||||
|
|
||||||
:host([disabled]) .radio-option:hover .radio-label {
|
:host([disabled]) .radio-option:hover .radio-label {
|
||||||
color: ${cssManager.bdTheme('#666', '#999')};
|
color: ${cssManager.bdTheme('hsl(215.3 25% 26.7%)', 'hsl(217.9 10.6% 74.9%)')};
|
||||||
}
|
}
|
||||||
|
|
||||||
.label-text {
|
.label-text {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
color: ${cssManager.bdTheme('#333', '#ccc')};
|
color: ${cssManager.bdTheme('hsl(215.3 25% 8.8%)', 'hsl(210 40% 98%)')};
|
||||||
margin-bottom: 8px;
|
margin-bottom: 10px;
|
||||||
|
letter-spacing: -0.006em;
|
||||||
|
line-height: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.description-text {
|
.description-text {
|
||||||
font-size: 12px;
|
font-size: 13px;
|
||||||
color: ${cssManager.bdTheme('#666', '#999')};
|
color: ${cssManager.bdTheme('hsl(215.4 16.3% 56.9%)', 'hsl(215 20.2% 55.1%)')};
|
||||||
margin-top: 8px;
|
margin-top: 10px;
|
||||||
line-height: 1.4;
|
line-height: 1.5;
|
||||||
|
letter-spacing: -0.003em;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Validation styles */
|
/* Validation styles */
|
||||||
:host([validationState="invalid"]) .radio-circle {
|
:host([validationState="invalid"]) .radio-circle {
|
||||||
border-color: #e74c3c;
|
border-color: ${cssManager.bdTheme('hsl(0 72.2% 50.6%)', 'hsl(0 62.8% 30.6%)')};
|
||||||
|
}
|
||||||
|
|
||||||
|
:host([validationState="invalid"]) .radio-option.selected .radio-circle {
|
||||||
|
border-color: ${cssManager.bdTheme('hsl(0 72.2% 50.6%)', 'hsl(0 62.8% 30.6%)')};
|
||||||
|
background: ${cssManager.bdTheme('hsl(0 72.2% 50.6%)', 'hsl(0 62.8% 30.6%)')};
|
||||||
}
|
}
|
||||||
|
|
||||||
:host([validationState="valid"]) .radio-option.selected .radio-circle {
|
:host([validationState="valid"]) .radio-option.selected .radio-circle {
|
||||||
border-color: #27ae60;
|
border-color: ${cssManager.bdTheme('hsl(142.1 70.6% 45.3%)', 'hsl(142.1 76.2% 36.3%)')};
|
||||||
background: #27ae60;
|
background: ${cssManager.bdTheme('hsl(142.1 70.6% 45.3%)', 'hsl(142.1 76.2% 36.3%)')};
|
||||||
}
|
}
|
||||||
|
|
||||||
:host([validationState="warn"]) .radio-option.selected .radio-circle {
|
:host([validationState="warn"]) .radio-option.selected .radio-circle {
|
||||||
border-color: #f39c12;
|
border-color: ${cssManager.bdTheme('hsl(45.4 93.4% 47.5%)', 'hsl(45.4 93.4% 47.5%)')};
|
||||||
background: #f39c12;
|
background: ${cssManager.bdTheme('hsl(45.4 93.4% 47.5%)', 'hsl(45.4 93.4% 47.5%)')};
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Override base grid layout for radiogroup to prevent large gaps */
|
/* Override base grid layout for radiogroup to prevent large gaps */
|
||||||
@ -212,8 +238,15 @@ export class DeesInputRadiogroup extends DeesInputBase<string | object> {
|
|||||||
<div
|
<div
|
||||||
class="radio-option ${isSelected ? 'selected' : ''}"
|
class="radio-option ${isSelected ? 'selected' : ''}"
|
||||||
@click="${() => this.selectOption(optionKey)}"
|
@click="${() => this.selectOption(optionKey)}"
|
||||||
|
@keydown="${(e: KeyboardEvent) => this.handleKeydown(e, optionKey)}"
|
||||||
>
|
>
|
||||||
<div class="radio-circle"></div>
|
<div
|
||||||
|
class="radio-circle"
|
||||||
|
tabindex="${this.disabled ? '-1' : '0'}"
|
||||||
|
role="radio"
|
||||||
|
aria-checked="${isSelected}"
|
||||||
|
aria-label="${optionLabel}"
|
||||||
|
></div>
|
||||||
<div class="radio-label">${optionLabel}</div>
|
<div class="radio-label">${optionLabel}</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
@ -292,4 +325,33 @@ export class DeesInputRadiogroup extends DeesInputBase<string | object> {
|
|||||||
this.selectedOption = this.getOptionKey(firstOption);
|
this.selectedOption = this.getOptionKey(firstOption);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private handleKeydown(event: KeyboardEvent, optionKey: string) {
|
||||||
|
if (this.disabled) return;
|
||||||
|
|
||||||
|
if (event.key === ' ' || event.key === 'Enter') {
|
||||||
|
event.preventDefault();
|
||||||
|
this.selectOption(optionKey);
|
||||||
|
} else if (event.key === 'ArrowDown' || event.key === 'ArrowRight') {
|
||||||
|
event.preventDefault();
|
||||||
|
this.focusNextOption();
|
||||||
|
} else if (event.key === 'ArrowUp' || event.key === 'ArrowLeft') {
|
||||||
|
event.preventDefault();
|
||||||
|
this.focusPreviousOption();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private focusNextOption() {
|
||||||
|
const radioCircles = Array.from(this.shadowRoot.querySelectorAll('.radio-circle'));
|
||||||
|
const currentIndex = radioCircles.findIndex(el => el === this.shadowRoot.activeElement);
|
||||||
|
const nextIndex = (currentIndex + 1) % radioCircles.length;
|
||||||
|
(radioCircles[nextIndex] as HTMLElement).focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
private focusPreviousOption() {
|
||||||
|
const radioCircles = Array.from(this.shadowRoot.querySelectorAll('.radio-circle'));
|
||||||
|
const currentIndex = radioCircles.findIndex(el => el === this.shadowRoot.activeElement);
|
||||||
|
const prevIndex = currentIndex <= 0 ? radioCircles.length - 1 : currentIndex - 1;
|
||||||
|
(radioCircles[prevIndex] as HTMLElement).focus();
|
||||||
|
}
|
||||||
}
|
}
|
@ -53,7 +53,7 @@ export class DeesInputTags extends DeesInputBase<DeesInputTags> {
|
|||||||
css`
|
css`
|
||||||
:host {
|
:host {
|
||||||
display: block;
|
display: block;
|
||||||
font-family: 'Geist Sans', sans-serif;
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
.input-wrapper {
|
.input-wrapper {
|
||||||
@ -64,44 +64,51 @@ export class DeesInputTags extends DeesInputBase<DeesInputTags> {
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 8px;
|
gap: 6px;
|
||||||
padding: 8px;
|
padding: 6px 10px;
|
||||||
min-height: 40px;
|
min-height: 40px;
|
||||||
background: ${cssManager.bdTheme('#fafafa', '#222222')};
|
background: transparent;
|
||||||
border: 1px solid ${cssManager.bdTheme('#e0e0e0', '#333333')};
|
border: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')};
|
||||||
border-radius: 8px;
|
border-radius: 6px;
|
||||||
transition: all 0.2s ease;
|
transition: all 0.15s ease;
|
||||||
cursor: text;
|
cursor: text;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tags-container:hover:not(.disabled) {
|
||||||
|
border-color: ${cssManager.bdTheme('hsl(0 0% 79.8%)', 'hsl(0 0% 20.9%)')};
|
||||||
|
}
|
||||||
|
|
||||||
.tags-container:focus-within {
|
.tags-container:focus-within {
|
||||||
border-color: ${cssManager.bdTheme('#0069f2', '#0084ff')};
|
border-color: ${cssManager.bdTheme('hsl(222.2 47.4% 51.2%)', 'hsl(217.2 91.2% 59.8%)')};
|
||||||
box-shadow: 0 0 0 2px ${cssManager.bdTheme('rgba(0, 105, 242, 0.1)', 'rgba(0, 132, 255, 0.2)')};
|
box-shadow: 0 0 0 3px ${cssManager.bdTheme('hsl(222.2 47.4% 51.2% / 0.1)', 'hsl(217.2 91.2% 59.8% / 0.1)')};
|
||||||
}
|
}
|
||||||
|
|
||||||
.tags-container.disabled {
|
.tags-container.disabled {
|
||||||
background: ${cssManager.bdTheme('#f5f5f5', '#1a1a1a')};
|
background: ${cssManager.bdTheme('hsl(0 0% 95.1%)', 'hsl(0 0% 14.9%)')};
|
||||||
|
border-color: ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')};
|
||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
opacity: 0.6;
|
opacity: 0.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tag {
|
.tag {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 4px;
|
gap: 4px;
|
||||||
padding: 4px 8px;
|
padding: 2px 8px;
|
||||||
background: ${cssManager.bdTheme('#e3f2fd', '#1e3a5f')};
|
background: ${cssManager.bdTheme('hsl(215 20.2% 65.1% / 0.2)', 'hsl(215 20.2% 35.1% / 0.2)')};
|
||||||
color: ${cssManager.bdTheme('#1976d2', '#90caf9')};
|
color: ${cssManager.bdTheme('hsl(215.3 25% 26.7%)', 'hsl(217.9 10.6% 74.9%)')};
|
||||||
border-radius: 16px;
|
border: 1px solid ${cssManager.bdTheme('hsl(215 20.2% 65.1% / 0.3)', 'hsl(215 20.2% 35.1% / 0.3)')};
|
||||||
font-size: 14px;
|
border-radius: 4px;
|
||||||
line-height: 1.2;
|
font-size: 13px;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 18px;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
animation: tagAppear 0.2s ease;
|
animation: tagAppear 0.15s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes tagAppear {
|
@keyframes tagAppear {
|
||||||
from {
|
from {
|
||||||
transform: scale(0.8);
|
transform: scale(0.95);
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
to {
|
to {
|
||||||
@ -114,21 +121,23 @@ export class DeesInputTags extends DeesInputBase<DeesInputTags> {
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
width: 16px;
|
width: 14px;
|
||||||
height: 16px;
|
height: 14px;
|
||||||
border-radius: 50%;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
margin-left: 2px;
|
margin-left: 2px;
|
||||||
|
border-radius: 3px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.15s ease;
|
||||||
|
color: ${cssManager.bdTheme('hsl(215.3 25% 46.7%)', 'hsl(217.9 10.6% 54.9%)')};
|
||||||
}
|
}
|
||||||
|
|
||||||
.tag-remove:hover {
|
.tag-remove:hover {
|
||||||
background: ${cssManager.bdTheme('rgba(0, 0, 0, 0.1)', 'rgba(255, 255, 255, 0.1)')};
|
background: ${cssManager.bdTheme('hsl(0 0% 0% / 0.08)', 'hsl(0 0% 100% / 0.08)')};
|
||||||
|
color: ${cssManager.bdTheme('hsl(215.3 25% 26.7%)', 'hsl(217.9 10.6% 74.9%)')};
|
||||||
}
|
}
|
||||||
|
|
||||||
.tag-remove dees-icon {
|
.tag-remove dees-icon {
|
||||||
width: 12px;
|
width: 10px;
|
||||||
height: 12px;
|
height: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tag-input {
|
.tag-input {
|
||||||
@ -139,12 +148,13 @@ export class DeesInputTags extends DeesInputBase<DeesInputTags> {
|
|||||||
outline: none;
|
outline: none;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
font-family: inherit;
|
font-family: inherit;
|
||||||
color: ${cssManager.bdTheme('#333', '#fff')};
|
color: ${cssManager.bdTheme('hsl(0 0% 9%)', 'hsl(0 0% 95%)')};
|
||||||
padding: 4px;
|
padding: 2px 4px;
|
||||||
|
line-height: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tag-input::placeholder {
|
.tag-input::placeholder {
|
||||||
color: ${cssManager.bdTheme('#999', '#666')};
|
color: ${cssManager.bdTheme('hsl(0 0% 63.9%)', 'hsl(0 0% 45.1%)')};
|
||||||
}
|
}
|
||||||
|
|
||||||
.tag-input:disabled {
|
.tag-input:disabled {
|
||||||
@ -162,44 +172,64 @@ export class DeesInputTags extends DeesInputBase<DeesInputTags> {
|
|||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
margin-top: 4px;
|
margin-top: 4px;
|
||||||
background: ${cssManager.bdTheme('#ffffff', '#222222')};
|
background: ${cssManager.bdTheme('hsl(0 0% 100%)', 'hsl(0 0% 3.9%)')};
|
||||||
border: 1px solid ${cssManager.bdTheme('#e0e0e0', '#333333')};
|
border: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')};
|
||||||
border-radius: 8px;
|
border-radius: 6px;
|
||||||
box-shadow: 0 4px 12px ${cssManager.bdTheme('rgba(0, 0, 0, 0.1)', 'rgba(0, 0, 0, 0.3)')};
|
box-shadow: 0 4px 6px -1px hsl(0 0% 0% / 0.1), 0 2px 4px -2px hsl(0 0% 0% / 0.1);
|
||||||
max-height: 200px;
|
max-height: 200px;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
z-index: 1000;
|
z-index: 1000;
|
||||||
}
|
}
|
||||||
|
|
||||||
.suggestion {
|
.suggestion {
|
||||||
padding: 8px 12px;
|
padding: 6px 10px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: all 0.2s ease;
|
transition: all 0.15s ease;
|
||||||
color: ${cssManager.bdTheme('#333', '#fff')};
|
font-size: 14px;
|
||||||
|
color: ${cssManager.bdTheme('hsl(0 0% 15%)', 'hsl(0 0% 90%)')};
|
||||||
}
|
}
|
||||||
|
|
||||||
.suggestion:hover,
|
.suggestion:hover {
|
||||||
.suggestion.highlighted {
|
background: ${cssManager.bdTheme('hsl(0 0% 95.1%)', 'hsl(0 0% 14.9%)')};
|
||||||
background: ${cssManager.bdTheme('#f5f5f5', '#333333')};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.suggestion.highlighted {
|
.suggestion.highlighted {
|
||||||
background: ${cssManager.bdTheme('#e3f2fd', '#1e3a5f')};
|
background: ${cssManager.bdTheme('hsl(210 40% 96.1%)', 'hsl(215 20.2% 16.8%)')};
|
||||||
|
color: ${cssManager.bdTheme('hsl(0 0% 9%)', 'hsl(0 0% 95%)')};
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Validation styles */
|
/* Validation styles */
|
||||||
.validation-message {
|
.validation-message {
|
||||||
color: #d32f2f;
|
color: ${cssManager.bdTheme('hsl(0 72.2% 50.6%)', 'hsl(0 62.8% 30.6%)')};
|
||||||
font-size: 12px;
|
font-size: 13px;
|
||||||
margin-top: 4px;
|
margin-top: 6px;
|
||||||
min-height: 16px;
|
line-height: 1.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Description styles */
|
/* Description styles */
|
||||||
.description {
|
.description {
|
||||||
color: ${cssManager.bdTheme('#666', '#999')};
|
color: ${cssManager.bdTheme('hsl(215.4 16.3% 56.9%)', 'hsl(215 20.2% 55.1%)')};
|
||||||
font-size: 12px;
|
font-size: 13px;
|
||||||
margin-top: 4px;
|
margin-top: 6px;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Scrollbar styling */
|
||||||
|
.suggestions-dropdown::-webkit-scrollbar {
|
||||||
|
width: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.suggestions-dropdown::-webkit-scrollbar-track {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.suggestions-dropdown::-webkit-scrollbar-thumb {
|
||||||
|
background: ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')};
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.suggestions-dropdown::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: ${cssManager.bdTheme('hsl(0 0% 79.8%)', 'hsl(0 0% 20.9%)')};
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { html, css } from '@design.estate/dees-element';
|
import { html, css, cssManager } from '@design.estate/dees-element';
|
||||||
import '@design.estate/dees-wcctools/demotools';
|
import '@design.estate/dees-wcctools/demotools';
|
||||||
|
import './dees-panel.js';
|
||||||
|
|
||||||
export const demoFunc = () => html`
|
export const demoFunc = () => html`
|
||||||
<dees-demowrapper>
|
<dees-demowrapper>
|
||||||
@ -14,36 +15,12 @@ export const demoFunc = () => html`
|
|||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.demo-section {
|
dees-panel {
|
||||||
background: #f8f9fa;
|
margin-bottom: 24px;
|
||||||
border-radius: 8px;
|
|
||||||
padding: 24px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (prefers-color-scheme: dark) {
|
dees-panel:last-child {
|
||||||
.demo-section {
|
margin-bottom: 0;
|
||||||
background: #1a1a1a;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.demo-section h3 {
|
|
||||||
margin-top: 0;
|
|
||||||
margin-bottom: 16px;
|
|
||||||
color: #0069f2;
|
|
||||||
font-size: 18px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.demo-section p {
|
|
||||||
margin-top: 0;
|
|
||||||
margin-bottom: 16px;
|
|
||||||
color: #666;
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (prefers-color-scheme: dark) {
|
|
||||||
.demo-section p {
|
|
||||||
color: #999;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.horizontal-group {
|
.horizontal-group {
|
||||||
@ -64,14 +41,28 @@ export const demoFunc = () => html`
|
|||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.interactive-section {
|
||||||
|
background: ${cssManager.bdTheme('hsl(210 40% 96.1%)', 'hsl(215 20.2% 16.8%)')};
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 16px;
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.output-text {
|
||||||
|
font-family: monospace;
|
||||||
|
font-size: 13px;
|
||||||
|
color: ${cssManager.bdTheme('hsl(215.3 25% 26.7%)', 'hsl(210 40% 80%)')};
|
||||||
|
padding: 8px;
|
||||||
|
background: ${cssManager.bdTheme('hsl(210 40% 98%)', 'hsl(215 20.2% 11.8%)')};
|
||||||
|
border-radius: 4px;
|
||||||
|
min-height: 24px;
|
||||||
|
}
|
||||||
`}
|
`}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<div class="demo-container">
|
<div class="demo-container">
|
||||||
<div class="demo-section">
|
<dees-panel .title=${'Basic Text Inputs'} .subtitle=${'Standard text inputs with labels and descriptions'}>
|
||||||
<h3>Basic Text Inputs</h3>
|
|
||||||
<p>Standard text inputs with labels and descriptions</p>
|
|
||||||
|
|
||||||
<dees-input-text
|
<dees-input-text
|
||||||
.label=${'Username'}
|
.label=${'Username'}
|
||||||
.value=${'johndoe'}
|
.value=${'johndoe'}
|
||||||
@ -91,12 +82,9 @@ export const demoFunc = () => html`
|
|||||||
.value=${'secret123'}
|
.value=${'secret123'}
|
||||||
.key=${'password'}
|
.key=${'password'}
|
||||||
></dees-input-text>
|
></dees-input-text>
|
||||||
</div>
|
</dees-panel>
|
||||||
|
|
||||||
<div class="demo-section">
|
|
||||||
<h3>Horizontal Layout</h3>
|
|
||||||
<p>Multiple inputs arranged horizontally for compact forms</p>
|
|
||||||
|
|
||||||
|
<dees-panel .title=${'Horizontal Layout'} .subtitle=${'Multiple inputs arranged horizontally for compact forms'}>
|
||||||
<div class="horizontal-group">
|
<div class="horizontal-group">
|
||||||
<dees-input-text
|
<dees-input-text
|
||||||
.label=${'First Name'}
|
.label=${'First Name'}
|
||||||
@ -119,12 +107,9 @@ export const demoFunc = () => html`
|
|||||||
.key=${'age'}
|
.key=${'age'}
|
||||||
></dees-input-text>
|
></dees-input-text>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</dees-panel>
|
||||||
|
|
||||||
<div class="demo-section">
|
|
||||||
<h3>Label Positions</h3>
|
|
||||||
<p>Different label positioning options for various layouts</p>
|
|
||||||
|
|
||||||
|
<dees-panel .title=${'Label Positions'} .subtitle=${'Different label positioning options for various layouts'}>
|
||||||
<dees-input-text
|
<dees-input-text
|
||||||
.label=${'Label on Top (Default)'}
|
.label=${'Label on Top (Default)'}
|
||||||
.value=${'Standard layout'}
|
.value=${'Standard layout'}
|
||||||
@ -150,12 +135,9 @@ export const demoFunc = () => html`
|
|||||||
.labelPosition=${'left'}
|
.labelPosition=${'left'}
|
||||||
></dees-input-text>
|
></dees-input-text>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</dees-panel>
|
||||||
|
|
||||||
<div class="demo-section">
|
|
||||||
<h3>Validation & States</h3>
|
|
||||||
<p>Different validation states and input configurations</p>
|
|
||||||
|
|
||||||
|
<dees-panel .title=${'Validation & States'} .subtitle=${'Different validation states and input configurations'}>
|
||||||
<dees-input-text
|
<dees-input-text
|
||||||
.label=${'Required Field'}
|
.label=${'Required Field'}
|
||||||
.required=${true}
|
.required=${true}
|
||||||
@ -174,12 +156,9 @@ export const demoFunc = () => html`
|
|||||||
.validationText=${'Please enter a valid email address'}
|
.validationText=${'Please enter a valid email address'}
|
||||||
.validationState=${'invalid'}
|
.validationState=${'invalid'}
|
||||||
></dees-input-text>
|
></dees-input-text>
|
||||||
</div>
|
</dees-panel>
|
||||||
|
|
||||||
<div class="demo-section">
|
|
||||||
<h3>Advanced Features</h3>
|
|
||||||
<p>Password visibility toggle and other advanced features</p>
|
|
||||||
|
|
||||||
|
<dees-panel .title=${'Advanced Features'} .subtitle=${'Password visibility toggle and other advanced features'}>
|
||||||
<dees-input-text
|
<dees-input-text
|
||||||
.label=${'Password with Toggle'}
|
.label=${'Password with Toggle'}
|
||||||
.isPasswordBool=${true}
|
.isPasswordBool=${true}
|
||||||
@ -193,7 +172,24 @@ export const demoFunc = () => html`
|
|||||||
.value=${'sk-1234567890abcdef'}
|
.value=${'sk-1234567890abcdef'}
|
||||||
.description=${'Keep this key secure and never share it'}
|
.description=${'Keep this key secure and never share it'}
|
||||||
></dees-input-text>
|
></dees-input-text>
|
||||||
|
</dees-panel>
|
||||||
|
|
||||||
|
<dees-panel .title=${'Interactive Example'} .subtitle=${'Try typing in the inputs to see real-time value changes'}>
|
||||||
|
<dees-input-text
|
||||||
|
.label=${'Dynamic Input'}
|
||||||
|
.placeholder=${'Type something here...'}
|
||||||
|
@changeSubject=${(event) => {
|
||||||
|
const output = document.querySelector('#text-input-output');
|
||||||
|
if (output && event.detail) {
|
||||||
|
output.textContent = `Current value: "${event.detail.getValue()}"`;
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
></dees-input-text>
|
||||||
|
|
||||||
|
<div class="interactive-section">
|
||||||
|
<div id="text-input-output" class="output-text">Current value: ""</div>
|
||||||
</div>
|
</div>
|
||||||
|
</dees-panel>
|
||||||
</div>
|
</div>
|
||||||
</dees-demowrapper>
|
</dees-demowrapper>
|
||||||
`;
|
`;
|
@ -65,77 +65,126 @@ export class DeesInputText extends DeesInputBase {
|
|||||||
:host {
|
:host {
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: auto;
|
z-index: auto;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
.maincontainer {
|
.maincontainer {
|
||||||
color: ${cssManager.bdTheme('#333', '#ccc')};
|
position: relative;
|
||||||
|
color: ${cssManager.bdTheme('hsl(0 0% 15%)', 'hsl(0 0% 90%)')};
|
||||||
}
|
}
|
||||||
|
|
||||||
input {
|
input {
|
||||||
margin-top: 0px;
|
display: flex;
|
||||||
background: ${cssManager.bdTheme('#fafafa', '#222')};
|
height: 40px;
|
||||||
border-top: ${cssManager.bdTheme('1px solid #CCC', '1px solid #ffffff10')};
|
|
||||||
border-bottom: ${cssManager.bdTheme('1px solid #CCC', '1px solid #222')};
|
|
||||||
border-right: ${cssManager.bdTheme('1px solid #CCC', 'none')};
|
|
||||||
border-left: ${cssManager.bdTheme('1px solid #CCC', 'none')};
|
|
||||||
padding-left: 10px;
|
|
||||||
padding-right: 10px;
|
|
||||||
border-radius: 2px;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
line-height: 36px;
|
padding: 0 12px;
|
||||||
transition: all 0.2s;
|
font-size: 14px;
|
||||||
|
line-height: 40px;
|
||||||
|
background: transparent;
|
||||||
|
border: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')};
|
||||||
|
border-radius: 6px;
|
||||||
|
transition: all 0.15s ease;
|
||||||
outline: none;
|
outline: none;
|
||||||
font-size: 16px;
|
cursor: text;
|
||||||
position: relative;
|
font-family: inherit;
|
||||||
z-index: 2;
|
color: ${cssManager.bdTheme('hsl(0 0% 9%)', 'hsl(0 0% 95%)')};
|
||||||
cursor: default;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
input:disabled {
|
input::placeholder {
|
||||||
background: ${cssManager.bdTheme('#ffffff00', '#11111100')};
|
color: ${cssManager.bdTheme('hsl(0 0% 63.9%)', 'hsl(0 0% 45.1%)')};
|
||||||
border: 1px dashed ${cssManager.bdTheme('#666666', '#666666')};
|
}
|
||||||
color: #9b9b9e;
|
|
||||||
cursor: default;
|
input:hover:not(:disabled):not(:focus) {
|
||||||
|
border-color: ${cssManager.bdTheme('hsl(0 0% 79.8%)', 'hsl(0 0% 20.9%)')};
|
||||||
}
|
}
|
||||||
|
|
||||||
input:focus {
|
input:focus {
|
||||||
outline: none;
|
outline: none;
|
||||||
border-bottom: 1px solid
|
border-color: ${cssManager.bdTheme('hsl(222.2 47.4% 51.2%)', 'hsl(217.2 91.2% 59.8%)')};
|
||||||
${cssManager.bdTheme(colors.bright.blueActive, colors.dark.blueActive)};
|
box-shadow: 0 0 0 3px ${cssManager.bdTheme('hsl(222.2 47.4% 51.2% / 0.1)', 'hsl(217.2 91.2% 59.8% / 0.1)')};
|
||||||
cursor: text;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
input:hover {
|
input:disabled {
|
||||||
filter: ${cssManager.bdTheme('brightness(0.95)', 'brightness(1.1)')};
|
background: ${cssManager.bdTheme('hsl(0 0% 95.1%)', 'hsl(0 0% 14.9%)')};
|
||||||
|
border-color: ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')};
|
||||||
|
color: ${cssManager.bdTheme('hsl(0 0% 63.9%)', 'hsl(0 0% 45.1%)')};
|
||||||
|
cursor: not-allowed;
|
||||||
|
opacity: 0.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Password toggle button */
|
||||||
.showPassword {
|
.showPassword {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 7px;
|
right: 1px;
|
||||||
right: 10px;
|
top: 50%;
|
||||||
border: 1px dashed #444;
|
transform: translateY(-50%);
|
||||||
border-radius: 7px;
|
display: flex;
|
||||||
padding: 4px 0px;
|
align-items: center;
|
||||||
width: 40px;
|
justify-content: center;
|
||||||
z-index: 3;
|
width: 38px;
|
||||||
text-align: center;
|
height: 38px;
|
||||||
|
cursor: pointer;
|
||||||
|
color: ${cssManager.bdTheme('hsl(0 0% 45.1%)', 'hsl(0 0% 63.9%)')};
|
||||||
|
transition: all 0.15s ease;
|
||||||
|
border-radius: 0 5px 5px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.showPassword:hover {
|
.showPassword:hover {
|
||||||
background: ${cssManager.bdTheme('#00000010', '#ffffff10')};
|
background: ${cssManager.bdTheme('hsl(0 0% 95.1%)', 'hsl(0 0% 14.9%)')};
|
||||||
|
color: ${cssManager.bdTheme('hsl(0 0% 15%)', 'hsl(0 0% 93.9%)')};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Validation styles */
|
||||||
.validationContainer {
|
.validationContainer {
|
||||||
text-align: center;
|
margin-top: 4px;
|
||||||
padding: 6px 2px 2px 2px;
|
padding: 4px 8px;
|
||||||
margin-top: -4px;
|
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: #fff;
|
font-weight: 500;
|
||||||
background: #e4002b;
|
border-radius: 4px;
|
||||||
position: relative;
|
transition: all 0.2s ease;
|
||||||
z-index: 1;
|
overflow: hidden;
|
||||||
border-radius: 3px;
|
}
|
||||||
transition: all 0.2s;
|
|
||||||
|
.validationContainer.error {
|
||||||
|
background: ${cssManager.bdTheme('hsl(0 84.2% 60.2% / 0.1)', 'hsl(0 72.2% 50.6% / 0.1)')};
|
||||||
|
color: ${cssManager.bdTheme('hsl(0 84.2% 60.2%)', 'hsl(0 72.2% 50.6%)')};
|
||||||
|
}
|
||||||
|
|
||||||
|
.validationContainer.warn {
|
||||||
|
background: ${cssManager.bdTheme('hsl(25 95% 53% / 0.1)', 'hsl(25 95% 63% / 0.1)')};
|
||||||
|
color: ${cssManager.bdTheme('hsl(25 95% 53%)', 'hsl(25 95% 63%)')};
|
||||||
|
}
|
||||||
|
|
||||||
|
.validationContainer.valid {
|
||||||
|
background: ${cssManager.bdTheme('hsl(142.1 76.2% 36.3% / 0.1)', 'hsl(142.1 70.6% 45.3% / 0.1)')};
|
||||||
|
color: ${cssManager.bdTheme('hsl(142.1 76.2% 36.3%)', 'hsl(142.1 70.6% 45.3%)')};
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Error state for input */
|
||||||
|
:host([validation-state="invalid"]) input {
|
||||||
|
border-color: ${cssManager.bdTheme('hsl(0 84.2% 60.2%)', 'hsl(0 72.2% 50.6%)')};
|
||||||
|
}
|
||||||
|
|
||||||
|
:host([validation-state="invalid"]) input:focus {
|
||||||
|
box-shadow: 0 0 0 3px ${cssManager.bdTheme('hsl(0 84.2% 60.2% / 0.1)', 'hsl(0 72.2% 50.6% / 0.1)')};
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Warning state for input */
|
||||||
|
:host([validation-state="warn"]) input {
|
||||||
|
border-color: ${cssManager.bdTheme('hsl(25 95% 53%)', 'hsl(25 95% 63%)')};
|
||||||
|
}
|
||||||
|
|
||||||
|
:host([validation-state="warn"]) input:focus {
|
||||||
|
box-shadow: 0 0 0 3px ${cssManager.bdTheme('hsl(25 95% 53% / 0.1)', 'hsl(25 95% 63% / 0.1)')};
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Valid state for input */
|
||||||
|
:host([validation-state="valid"]) input {
|
||||||
|
border-color: ${cssManager.bdTheme('hsl(142.1 76.2% 36.3%)', 'hsl(142.1 70.6% 45.3%)')};
|
||||||
|
}
|
||||||
|
|
||||||
|
:host([validation-state="valid"]) input:focus {
|
||||||
|
box-shadow: 0 0 0 3px ${cssManager.bdTheme('hsl(142.1 76.2% 36.3% / 0.1)', 'hsl(142.1 70.6% 45.3% / 0.1)')};
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
@ -144,42 +193,51 @@ export class DeesInputText extends DeesInputBase {
|
|||||||
return html`
|
return html`
|
||||||
<style>
|
<style>
|
||||||
input {
|
input {
|
||||||
font-family: ${this.isPasswordBool ? 'monospace' : 'Geist Sans'};
|
font-family: ${this.isPasswordBool ? 'SF Mono, Monaco, Consolas, Liberation Mono, Courier New, monospace' : 'inherit'};
|
||||||
letter-spacing: ${this.isPasswordBool ? '1px' : 'normal'};
|
letter-spacing: ${this.isPasswordBool ? '0.5px' : 'normal'};
|
||||||
color: ${this.goBright ? '#333' : '#ccc'};
|
padding-right: ${this.isPasswordBool ? '48px' : '12px'};
|
||||||
}
|
}
|
||||||
${this.validationText
|
${this.validationText
|
||||||
? css`
|
? css`
|
||||||
.validationContainer {
|
.validationContainer {
|
||||||
height: 22px;
|
height: auto;
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
: css`
|
: css`
|
||||||
.validationContainer {
|
.validationContainer {
|
||||||
height: 4px;
|
height: 0;
|
||||||
padding: 2px !important;
|
padding: 0 !important;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
|
transform: translateY(-4px);
|
||||||
}
|
}
|
||||||
`}
|
`}
|
||||||
</style>
|
</style>
|
||||||
<div class="input-wrapper">
|
<div class="input-wrapper">
|
||||||
<dees-label .label=${this.label} .description=${this.description}></dees-label>
|
<dees-label .label=${this.label} .description=${this.description} .required=${this.required}></dees-label>
|
||||||
<div class="maincontainer">
|
<div class="maincontainer">
|
||||||
<input
|
<input
|
||||||
type="${this.isPasswordBool && !this.showPasswordBool ? 'password' : 'text'}"
|
type="${this.isPasswordBool && !this.showPasswordBool ? 'password' : 'text'}"
|
||||||
.value=${this.value}
|
.value=${this.value}
|
||||||
@input="${this.updateValue}"
|
@input="${this.updateValue}"
|
||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
|
placeholder="${this.label ? '' : 'Enter text...'}"
|
||||||
/>
|
/>
|
||||||
<div class="validationContainer">${this.validationText}</div>
|
|
||||||
${this.isPasswordBool
|
${this.isPasswordBool
|
||||||
? html`
|
? html`
|
||||||
<div class="showPassword" @click=${this.togglePasswordView}>
|
<div class="showPassword" @click=${this.togglePasswordView}>
|
||||||
<dees-icon .iconFA=${this.showPasswordBool ? 'eye' : 'eyeSlash'}></dees-icon>
|
<dees-icon .iconName=${this.showPasswordBool ? 'lucideEye' : 'lucideEyeOff'}></dees-icon>
|
||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
: html``}
|
: html``}
|
||||||
|
${this.validationText
|
||||||
|
? html`
|
||||||
|
<div class="validationContainer ${this.validationState || 'error'}">
|
||||||
|
${this.validationText}
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
: html`<div class="validationContainer"></div>`}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
@ -205,7 +263,6 @@ export class DeesInputText extends DeesInputBase {
|
|||||||
|
|
||||||
public async togglePasswordView() {
|
public async togglePasswordView() {
|
||||||
this.showPasswordBool = !this.showPasswordBool;
|
this.showPasswordBool = !this.showPasswordBool;
|
||||||
console.log(`this.showPasswordBool is: ${this.showPasswordBool}`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async focus() {
|
public async focus() {
|
||||||
|
@ -10,7 +10,7 @@ import {
|
|||||||
import * as domtools from '@design.estate/dees-domtools';
|
import * as domtools from '@design.estate/dees-domtools';
|
||||||
import { DeesInputBase } from './dees-input-base.js';
|
import { DeesInputBase } from './dees-input-base.js';
|
||||||
|
|
||||||
const { demoFunc } = await import('./dees-input-typelist.demo.js');
|
import { demoFunc } from './dees-input-typelist.demo.js';
|
||||||
|
|
||||||
@customElement('dees-input-typelist')
|
@customElement('dees-input-typelist')
|
||||||
export class DeesInputTypelist extends DeesInputBase<DeesInputTypelist> {
|
export class DeesInputTypelist extends DeesInputBase<DeesInputTypelist> {
|
||||||
@ -35,12 +35,24 @@ export class DeesInputTypelist extends DeesInputBase<DeesInputTypelist> {
|
|||||||
}
|
}
|
||||||
.mainbox {
|
.mainbox {
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
background: #222;
|
background: ${cssManager.bdTheme('#fafafa', '#222222')};
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
border-top: ${cssManager.bdTheme('1px solid #CCC', '1px solid #444')};
|
border-top: ${cssManager.bdTheme('1px solid #CCC', '1px solid #ffffff10')};
|
||||||
border-bottom: ${cssManager.bdTheme('1px solid #CCC', '1px solid #333')};
|
border-bottom: ${cssManager.bdTheme('1px solid #CCC', '1px solid #222')};
|
||||||
border-right: ${cssManager.bdTheme('1px solid #CCC', 'none')};
|
border-right: ${cssManager.bdTheme('1px solid #CCC', '1px solid #ffffff10')};
|
||||||
border-left: ${cssManager.bdTheme('1px solid #CCC', 'none')};
|
border-left: ${cssManager.bdTheme('1px solid #CCC', '1px solid #ffffff10')};
|
||||||
|
box-shadow: ${cssManager.bdTheme('0px 1px 4px rgba(0,0,0,0.3)', 'none')};
|
||||||
|
transition: all 0.2s;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mainbox:hover {
|
||||||
|
filter: ${cssManager.bdTheme('brightness(0.98)', 'brightness(1.05)')};
|
||||||
|
}
|
||||||
|
|
||||||
|
.mainbox:focus-within {
|
||||||
|
outline: 2px solid ${cssManager.bdTheme('#0069f2', '#0084ff')};
|
||||||
|
outline-offset: -2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tags {
|
.tags {
|
||||||
@ -50,14 +62,15 @@ export class DeesInputTypelist extends DeesInputBase<DeesInputTypelist> {
|
|||||||
|
|
||||||
.notags {
|
.notags {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
opacity: 0.5;
|
color: ${cssManager.bdTheme('#999', '#666')};
|
||||||
font-size: 12px;
|
font-size: 13px;
|
||||||
|
font-style: italic;
|
||||||
}
|
}
|
||||||
|
|
||||||
input {
|
input {
|
||||||
display: block;
|
display: block;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
background: #181818;
|
background: ${cssManager.bdTheme('#f5f5f5', '#181818')};
|
||||||
width: 100%;
|
width: 100%;
|
||||||
outline: none;
|
outline: none;
|
||||||
border: none;
|
border: none;
|
||||||
@ -67,30 +80,68 @@ export class DeesInputTypelist extends DeesInputBase<DeesInputTypelist> {
|
|||||||
line-height: 32px;
|
line-height: 32px;
|
||||||
height: 0px;
|
height: 0px;
|
||||||
transition: height 0.2s;
|
transition: height 0.2s;
|
||||||
|
border-top: 1px solid ${cssManager.bdTheme('#e0e0e0', '#333')};
|
||||||
}
|
}
|
||||||
|
|
||||||
input:focus {
|
input:focus {
|
||||||
height: 32px;
|
height: 32px;
|
||||||
|
background: ${cssManager.bdTheme('#fafafa', '#1a1a1a')};
|
||||||
|
}
|
||||||
|
|
||||||
|
input::placeholder {
|
||||||
|
color: ${cssManager.bdTheme('#999', '#666')};
|
||||||
}
|
}
|
||||||
|
|
||||||
.tag {
|
.tag {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
background: ${cssManager.bdTheme('#e0e0e0', '#444')};
|
background: ${cssManager.bdTheme('#e8f5e9', '#2d3a2d')};
|
||||||
color: ${cssManager.bdTheme('#333', '#fff')};
|
color: ${cssManager.bdTheme('#2e7d32', '#81c784')};
|
||||||
padding: 4px 8px;
|
padding: 4px 10px;
|
||||||
border-radius: 3px;
|
border-radius: 4px;
|
||||||
margin: 2px;
|
margin: 3px;
|
||||||
font-size: 12px;
|
font-size: 13px;
|
||||||
|
font-weight: 500;
|
||||||
|
transition: all 0.2s;
|
||||||
|
border: 1px solid ${cssManager.bdTheme('#c8e6c9', '#1b5e20')};
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag:hover {
|
||||||
|
background: ${cssManager.bdTheme('#c8e6c9', '#3d4f3d')};
|
||||||
|
transform: translateY(-1px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.tag .remove {
|
.tag .remove {
|
||||||
margin-left: 6px;
|
margin-left: 8px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
opacity: 0.6;
|
opacity: 0.7;
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 16px;
|
||||||
|
line-height: 1;
|
||||||
|
transition: opacity 0.2s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tag .remove:hover {
|
.tag .remove:hover {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
|
color: ${cssManager.bdTheme('#c62828', '#ef5350')};
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Disabled state */
|
||||||
|
:host([disabled]) .mainbox {
|
||||||
|
opacity: 0.6;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
:host([disabled]) .tags {
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
:host([disabled]) .tag {
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
:host([disabled]) input {
|
||||||
|
cursor: not-allowed;
|
||||||
|
background: ${cssManager.bdTheme('#f0f0f0', '#1a1a1a')};
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
|
@ -32,20 +32,43 @@ export class DeesLabel extends DeesElement {
|
|||||||
})
|
})
|
||||||
public description: string;
|
public description: string;
|
||||||
|
|
||||||
|
@property({
|
||||||
|
type: Boolean,
|
||||||
|
reflect: true,
|
||||||
|
})
|
||||||
|
public required: boolean = false;
|
||||||
|
|
||||||
public static styles = [
|
public static styles = [
|
||||||
cssManager.defaultStyles,
|
cssManager.defaultStyles,
|
||||||
css`
|
css`
|
||||||
|
:host {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
.label {
|
.label {
|
||||||
color: ${cssManager.bdTheme('#333', '#ccc')};
|
display: inline-block;
|
||||||
|
color: ${cssManager.bdTheme('hsl(0 0% 15%)', 'hsl(0 0% 90%)')};
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
margin-bottom: 8px;
|
font-weight: 500;
|
||||||
|
line-height: 1.5;
|
||||||
|
margin-bottom: 6px;
|
||||||
cursor: default;
|
cursor: default;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
|
letter-spacing: -0.01em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.required {
|
||||||
|
color: ${cssManager.bdTheme('hsl(0 84.2% 60.2%)', 'hsl(0 72.2% 50.6%)')};
|
||||||
|
margin-left: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
dees-icon {
|
dees-icon {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
font-size: 14px;
|
font-size: 12px;
|
||||||
transform: translateY(1.5px);
|
transform: translateY(1px);
|
||||||
|
margin-left: 4px;
|
||||||
|
color: ${cssManager.bdTheme('hsl(0 0% 45.1%)', 'hsl(0 0% 63.9%)')};
|
||||||
|
cursor: help;
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
@ -56,9 +79,10 @@ export class DeesLabel extends DeesElement {
|
|||||||
? html`
|
? html`
|
||||||
<div class="label">
|
<div class="label">
|
||||||
${this.label}
|
${this.label}
|
||||||
|
${this.required ? html`<span class="required">*</span>` : ''}
|
||||||
${this.description
|
${this.description
|
||||||
? html`
|
? html`
|
||||||
<dees-icon .iconFA=${'circleInfo'}></dees-icon>
|
<dees-icon .iconName=${'lucideInfo'}></dees-icon>
|
||||||
<dees-speechbubble .text=${this.description}></dees-speechbubble>
|
<dees-speechbubble .text=${this.description}></dees-speechbubble>
|
||||||
`
|
`
|
||||||
: html``}
|
: html``}
|
||||||
|
@ -42,6 +42,7 @@ export class DeesModal extends DeesElement {
|
|||||||
showCloseButton?: boolean;
|
showCloseButton?: boolean;
|
||||||
showHelpButton?: boolean;
|
showHelpButton?: boolean;
|
||||||
onHelp?: () => void | Promise<void>;
|
onHelp?: () => void | Promise<void>;
|
||||||
|
mobileFullscreen?: boolean;
|
||||||
}) {
|
}) {
|
||||||
const body = document.body;
|
const body = document.body;
|
||||||
const modal = new DeesModal();
|
const modal = new DeesModal();
|
||||||
@ -54,6 +55,7 @@ export class DeesModal extends DeesElement {
|
|||||||
if (optionsArg.showCloseButton !== undefined) modal.showCloseButton = optionsArg.showCloseButton;
|
if (optionsArg.showCloseButton !== undefined) modal.showCloseButton = optionsArg.showCloseButton;
|
||||||
if (optionsArg.showHelpButton !== undefined) modal.showHelpButton = optionsArg.showHelpButton;
|
if (optionsArg.showHelpButton !== undefined) modal.showHelpButton = optionsArg.showHelpButton;
|
||||||
if (optionsArg.onHelp) modal.onHelp = optionsArg.onHelp;
|
if (optionsArg.onHelp) modal.onHelp = optionsArg.onHelp;
|
||||||
|
if (optionsArg.mobileFullscreen !== undefined) modal.mobileFullscreen = optionsArg.mobileFullscreen;
|
||||||
modal.windowLayer = await DeesWindowLayer.createAndShow({
|
modal.windowLayer = await DeesWindowLayer.createAndShow({
|
||||||
blur: true,
|
blur: true,
|
||||||
});
|
});
|
||||||
@ -101,6 +103,9 @@ export class DeesModal extends DeesElement {
|
|||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
public onHelp: () => void | Promise<void>;
|
public onHelp: () => void | Promise<void>;
|
||||||
|
|
||||||
|
@property({ type: Boolean })
|
||||||
|
public mobileFullscreen: boolean = false;
|
||||||
|
|
||||||
@state()
|
@state()
|
||||||
private modalZIndex: number = 1000;
|
private modalZIndex: number = 1000;
|
||||||
|
|
||||||
@ -132,6 +137,7 @@ export class DeesModal extends DeesElement {
|
|||||||
transform: translateY(0px) scale(0.95);
|
transform: translateY(0px) scale(0.95);
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
min-height: 120px;
|
min-height: 120px;
|
||||||
|
max-height: calc(100vh - 40px);
|
||||||
background: ${cssManager.bdTheme('#ffffff', '#111')};
|
background: ${cssManager.bdTheme('#ffffff', '#111')};
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
border: 1px solid ${cssManager.bdTheme('#e0e0e0', '#333')};
|
border: 1px solid ${cssManager.bdTheme('#e0e0e0', '#333')};
|
||||||
@ -139,6 +145,9 @@ export class DeesModal extends DeesElement {
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
box-shadow: ${cssManager.bdTheme('0px 2px 10px rgba(0, 0, 0, 0.1)', '0px 2px 5px rgba(0, 0, 0, 0.5)')};
|
box-shadow: ${cssManager.bdTheme('0px 2px 10px rgba(0, 0, 0, 0.1)', '0px 2px 5px rgba(0, 0, 0, 0.5)')};
|
||||||
margin: 20px;
|
margin: 20px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overscroll-behavior: contain;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Width variations */
|
/* Width variations */
|
||||||
@ -165,6 +174,25 @@ export class DeesModal extends DeesElement {
|
|||||||
width: calc(100vw - 40px) !important;
|
width: calc(100vw - 40px) !important;
|
||||||
max-width: none !important;
|
max-width: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Allow full height on mobile when content needs it */
|
||||||
|
.modalContainer {
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal {
|
||||||
|
margin: 10px;
|
||||||
|
max-height: calc(100vh - 20px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Full screen mode on mobile */
|
||||||
|
.modal.mobile-fullscreen {
|
||||||
|
width: 100vw !important;
|
||||||
|
height: 100vh !important;
|
||||||
|
max-height: 100vh !important;
|
||||||
|
margin: 0;
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal.show {
|
.modal.show {
|
||||||
@ -179,6 +207,7 @@ export class DeesModal extends DeesElement {
|
|||||||
|
|
||||||
.modal .heading {
|
.modal .heading {
|
||||||
height: 40px;
|
height: 40px;
|
||||||
|
min-height: 40px;
|
||||||
font-family: 'Geist Sans', sans-serif;
|
font-family: 'Geist Sans', sans-serif;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@ -186,6 +215,7 @@ export class DeesModal extends DeesElement {
|
|||||||
padding: 0 12px;
|
padding: 0 12px;
|
||||||
border-bottom: 1px solid ${cssManager.bdTheme('#e0e0e0', '#333')};
|
border-bottom: 1px solid ${cssManager.bdTheme('#e0e0e0', '#333')};
|
||||||
position: relative;
|
position: relative;
|
||||||
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal .heading .header-buttons {
|
.modal .heading .header-buttons {
|
||||||
@ -237,6 +267,10 @@ export class DeesModal extends DeesElement {
|
|||||||
|
|
||||||
.modal .content {
|
.modal .content {
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
|
flex: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
overscroll-behavior: contain;
|
||||||
}
|
}
|
||||||
.modal .bottomButtons {
|
.modal .bottomButtons {
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -245,6 +279,7 @@ export class DeesModal extends DeesElement {
|
|||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal .bottomButtons .bottomButton {
|
.modal .bottomButtons .bottomButton {
|
||||||
@ -291,6 +326,7 @@ export class DeesModal extends DeesElement {
|
|||||||
const customWidth = typeof this.width === 'number' ? `${this.width}px` : '';
|
const customWidth = typeof this.width === 'number' ? `${this.width}px` : '';
|
||||||
const maxWidthStyle = this.maxWidth ? `${this.maxWidth}px` : '';
|
const maxWidthStyle = this.maxWidth ? `${this.maxWidth}px` : '';
|
||||||
const minWidthStyle = this.minWidth ? `${this.minWidth}px` : '';
|
const minWidthStyle = this.minWidth ? `${this.minWidth}px` : '';
|
||||||
|
const mobileFullscreenClass = this.mobileFullscreen ? 'mobile-fullscreen' : '';
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<style>
|
<style>
|
||||||
@ -299,7 +335,7 @@ export class DeesModal extends DeesElement {
|
|||||||
${minWidthStyle ? `.modal { min-width: ${minWidthStyle}; }` : ''}
|
${minWidthStyle ? `.modal { min-width: ${minWidthStyle}; }` : ''}
|
||||||
</style>
|
</style>
|
||||||
<div class="modalContainer" @click=${this.handleOutsideClick} style="z-index: ${this.modalZIndex}">
|
<div class="modalContainer" @click=${this.handleOutsideClick} style="z-index: ${this.modalZIndex}">
|
||||||
<div class="modal ${widthClass}">
|
<div class="modal ${widthClass} ${mobileFullscreenClass}">
|
||||||
<div class="heading">
|
<div class="heading">
|
||||||
<div class="heading-text">${this.heading}</div>
|
<div class="heading-text">${this.heading}</div>
|
||||||
<div class="header-buttons">
|
<div class="header-buttons">
|
||||||
|
@ -5,7 +5,7 @@ export const demoFunc = () => html`
|
|||||||
${css`
|
${css`
|
||||||
.demo-background {
|
.demo-background {
|
||||||
padding: 24px;
|
padding: 24px;
|
||||||
background: ${cssManager.bdTheme('#f0f0f0', '#0a0a0a')};
|
background: ${cssManager.bdTheme('hsl(0 0% 95%)', 'hsl(0 0% 5%)')};
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -17,65 +17,156 @@ export const demoFunc = () => html`
|
|||||||
gap: 24px;
|
gap: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.section-title {
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: 700;
|
||||||
|
margin: 32px 0 16px 0;
|
||||||
|
color: ${cssManager.bdTheme('hsl(0 0% 9%)', 'hsl(0 0% 95%)')};
|
||||||
|
letter-spacing: -0.025em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-title:first-child {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.grid-layout {
|
.grid-layout {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr 1fr;
|
grid-template-columns: 1fr 1fr;
|
||||||
gap: 24px;
|
gap: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.grid-3col {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr 1fr;
|
||||||
|
gap: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 968px) {
|
||||||
|
.grid-3col {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.grid-layout {
|
.grid-layout {
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
code {
|
||||||
|
background: ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')};
|
||||||
|
padding: 2px 6px;
|
||||||
|
border-radius: 3px;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
`}
|
`}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<div class="demo-background">
|
<div class="demo-background">
|
||||||
<div class="demo-container">
|
<div class="demo-container">
|
||||||
<dees-panel .title=${'Panel Component'}>
|
<h2 class="section-title">Default Panels</h2>
|
||||||
|
|
||||||
|
<dees-panel .title=${'Panel Component'} .subtitle=${'The default panel variant with shadcn-inspired styling'}>
|
||||||
<p>The panel component automatically follows the theme and provides consistent styling for grouped content.</p>
|
<p>The panel component automatically follows the theme and provides consistent styling for grouped content.</p>
|
||||||
<p>It's perfect for creating sections in your application with proper spacing and borders.</p>
|
<p>It's perfect for creating sections in your application with proper spacing and borders.</p>
|
||||||
</dees-panel>
|
</dees-panel>
|
||||||
|
|
||||||
<dees-panel .title=${'Panel with Subtitle'} .subtitle=${'Additional context information'}>
|
|
||||||
<p>Panels can have both a title and subtitle to provide more context.</p>
|
|
||||||
<p>The subtitle appears in a smaller, muted text below the title.</p>
|
|
||||||
</dees-panel>
|
|
||||||
|
|
||||||
<div class="grid-layout">
|
<div class="grid-layout">
|
||||||
<dees-panel .title=${'Feature 1'}>
|
<dees-panel .title=${'Feature Overview'} .subtitle=${'Key capabilities'}>
|
||||||
<p>Grid layouts work great with panels for creating dashboards and feature sections.</p>
|
<p>Grid layouts work great with panels for creating dashboards and feature sections.</p>
|
||||||
<dees-button>Action</dees-button>
|
<dees-button>Learn More</dees-button>
|
||||||
</dees-panel>
|
</dees-panel>
|
||||||
|
|
||||||
<dees-panel .title=${'Feature 2'}>
|
<dees-panel .title=${'Quick Actions'} .subtitle=${'Common tasks'}>
|
||||||
<p>Each panel maintains consistent spacing and styling.</p>
|
<p>Each panel maintains consistent spacing and styling across your application.</p>
|
||||||
<dees-button>Another Action</dees-button>
|
<dees-button>Get Started</dees-button>
|
||||||
</dees-panel>
|
</dees-panel>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<dees-panel .title=${'Complex Content'}>
|
<h2 class="section-title">Panel Variants</h2>
|
||||||
<h4>Nested Elements</h4>
|
|
||||||
<p>Panels can contain any type of content:</p>
|
|
||||||
<ul>
|
|
||||||
<li>Text and paragraphs</li>
|
|
||||||
<li>Lists and tables</li>
|
|
||||||
<li>Form inputs</li>
|
|
||||||
<li>Other components</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<dees-input-text .label=${'Example Input'} .description=${'Input inside a panel'}></dees-input-text>
|
<dees-panel .title=${'Default Variant'} .variant=${'default'}>
|
||||||
|
<p>The default variant has a white background, subtle border, and minimal shadow. It's the standard choice for most content.</p>
|
||||||
<div style="margin-top: 16px;">
|
<p>Use <code>variant="default"</code> or omit the variant property.</p>
|
||||||
<dees-button>Submit</dees-button>
|
|
||||||
</div>
|
|
||||||
</dees-panel>
|
</dees-panel>
|
||||||
|
|
||||||
|
<dees-panel .title=${'Outline Variant'} .subtitle=${'Transparent background with border'} .variant=${'outline'}>
|
||||||
|
<p>The outline variant removes the background color and shadow, keeping only the border.</p>
|
||||||
|
<p>Use <code>variant="outline"</code> for a lighter visual weight.</p>
|
||||||
|
</dees-panel>
|
||||||
|
|
||||||
|
<dees-panel .title=${'Ghost Variant'} .subtitle=${'Minimal styling for subtle sections'} .variant=${'ghost'}>
|
||||||
|
<p>The ghost variant has no border or background by default, only showing a subtle background on hover.</p>
|
||||||
|
<p>Use <code>variant="ghost"</code> for the most minimal appearance.</p>
|
||||||
|
</dees-panel>
|
||||||
|
|
||||||
|
<h2 class="section-title">Panel Sizes</h2>
|
||||||
|
|
||||||
|
<div class="grid-3col">
|
||||||
|
<dees-panel .title=${'Small Panel'} .size=${'sm'}>
|
||||||
|
<p>Compact padding for dense layouts.</p>
|
||||||
|
<p>Use <code>size="sm"</code></p>
|
||||||
|
</dees-panel>
|
||||||
|
|
||||||
|
<dees-panel .title=${'Medium Panel'} .size=${'md'}>
|
||||||
|
<p>Default size with balanced spacing.</p>
|
||||||
|
<p>Use <code>size="md"</code> or omit.</p>
|
||||||
|
</dees-panel>
|
||||||
|
|
||||||
|
<dees-panel .title=${'Large Panel'} .size=${'lg'}>
|
||||||
|
<p>Generous padding for prominent sections.</p>
|
||||||
|
<p>Use <code>size="lg"</code></p>
|
||||||
|
</dees-panel>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2 class="section-title">Complex Examples</h2>
|
||||||
|
|
||||||
|
<dees-panel .title=${'Form Example'} .subtitle=${'Panels work great for organizing form sections'}>
|
||||||
|
<dees-form>
|
||||||
|
<dees-input-text .label=${'Project Name'} .required=${true}></dees-input-text>
|
||||||
|
<dees-input-text .label=${'Description'} .inputType=${'textarea'}></dees-input-text>
|
||||||
|
<dees-input-dropdown
|
||||||
|
.label=${'Category'}
|
||||||
|
.options=${[
|
||||||
|
{ option: 'Web Development', key: 'web' },
|
||||||
|
{ option: 'Mobile App', key: 'mobile' },
|
||||||
|
{ option: 'Desktop Software', key: 'desktop' }
|
||||||
|
]}
|
||||||
|
></dees-input-dropdown>
|
||||||
|
<dees-form-submit>Create Project</dees-form-submit>
|
||||||
|
</dees-form>
|
||||||
|
</dees-panel>
|
||||||
|
|
||||||
|
<dees-panel .title=${'Nested Panels'} .subtitle=${'Panels can be nested for hierarchical organization'}>
|
||||||
|
<p>You can nest panels to create more complex layouts:</p>
|
||||||
|
|
||||||
|
<dees-panel .title=${'Nested Panel 1'} .variant=${'outline'} .size=${'sm'}>
|
||||||
|
<p>This is a nested panel with outline variant and small size.</p>
|
||||||
|
</dees-panel>
|
||||||
|
|
||||||
|
<dees-panel .title=${'Nested Panel 2'} .variant=${'ghost'} .size=${'sm'}>
|
||||||
|
<p>This is another nested panel with ghost variant.</p>
|
||||||
|
</dees-panel>
|
||||||
|
</dees-panel>
|
||||||
|
|
||||||
|
<h2 class="section-title">Untitled Panels</h2>
|
||||||
|
|
||||||
<dees-panel>
|
<dees-panel>
|
||||||
<p>Panels work great even without a title for simple content grouping.</p>
|
<p>Panels work great even without a title for simple content grouping.</p>
|
||||||
<p>They provide visual separation and consistent padding.</p>
|
<p>They provide visual separation and consistent padding throughout your interface.</p>
|
||||||
|
</dees-panel>
|
||||||
|
|
||||||
|
<div class="grid-layout">
|
||||||
|
<dees-panel .variant=${'outline'}>
|
||||||
|
<h4 style="margin-top: 0;">Custom Content</h4>
|
||||||
|
<p>You can add your own headings and structure within untitled panels.</p>
|
||||||
|
</dees-panel>
|
||||||
|
|
||||||
|
<dees-panel .variant=${'ghost'}>
|
||||||
|
<h4 style="margin-top: 0;">Minimal Style</h4>
|
||||||
|
<p>Ghost panels without titles create very subtle content sections.</p>
|
||||||
</dees-panel>
|
</dees-panel>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
`;
|
`;
|
@ -8,6 +8,7 @@ import {
|
|||||||
type TemplateResult,
|
type TemplateResult,
|
||||||
} from '@design.estate/dees-element';
|
} from '@design.estate/dees-element';
|
||||||
import { demoFunc } from './dees-panel.demo.js';
|
import { demoFunc } from './dees-panel.demo.js';
|
||||||
|
import { cssGeistFontFamily } from './00fonts.js';
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface HTMLElementTagNameMap {
|
interface HTMLElementTagNameMap {
|
||||||
@ -25,33 +26,113 @@ export class DeesPanel extends DeesElement {
|
|||||||
@property({ type: String })
|
@property({ type: String })
|
||||||
public subtitle: string = '';
|
public subtitle: string = '';
|
||||||
|
|
||||||
|
@property({ type: String })
|
||||||
|
public variant: 'default' | 'outline' | 'ghost' = 'default';
|
||||||
|
|
||||||
|
@property({ type: String })
|
||||||
|
public size: 'sm' | 'md' | 'lg' = 'md';
|
||||||
|
|
||||||
|
@property({ attribute: false })
|
||||||
|
public runAfterRender?: (elementArg: HTMLElement) => void | Promise<void>;
|
||||||
|
|
||||||
public static styles = [
|
public static styles = [
|
||||||
cssManager.defaultStyles,
|
cssManager.defaultStyles,
|
||||||
css`
|
css`
|
||||||
:host {
|
:host {
|
||||||
display: block;
|
display: block;
|
||||||
background: ${cssManager.bdTheme('#ffffff', '#1a1a1a')};
|
font-family: ${cssGeistFontFamily};
|
||||||
border-radius: 8px;
|
background: ${cssManager.bdTheme('hsl(0 0% 100%)', 'hsl(0 0% 3.9%)')};
|
||||||
|
border-radius: 6px;
|
||||||
padding: 24px;
|
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('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')};
|
||||||
border: 1px solid ${cssManager.bdTheme('rgba(0,0,0,0.1)', 'rgba(255,255,255,0.1)')};
|
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Variant: default */
|
||||||
|
:host([variant="default"]) {
|
||||||
|
box-shadow: 0 1px 2px 0 hsl(0 0% 0% / 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Variant: outline */
|
||||||
|
:host([variant="outline"]) {
|
||||||
|
background: transparent;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Variant: ghost */
|
||||||
|
:host([variant="ghost"]) {
|
||||||
|
background: transparent;
|
||||||
|
border-color: transparent;
|
||||||
|
box-shadow: none;
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Size variations */
|
||||||
|
:host([size="sm"]) {
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:host([size="lg"]) {
|
||||||
|
padding: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header:empty {
|
||||||
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
margin: 0 0 16px 0;
|
margin: 0;
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
font-weight: 500;
|
font-weight: 600;
|
||||||
color: ${cssManager.bdTheme('#0069f2', '#0099ff')};
|
color: ${cssManager.bdTheme('hsl(0 0% 9%)', 'hsl(0 0% 95%)')};
|
||||||
|
letter-spacing: -0.025em;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Title size variations */
|
||||||
|
:host([size="sm"]) .title {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:host([size="lg"]) .title {
|
||||||
|
font-size: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.subtitle {
|
.subtitle {
|
||||||
margin: -12px 0 16px 0;
|
margin: 4px 0 0 0;
|
||||||
font-size: 14px;
|
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.006em;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Subtitle size variations */
|
||||||
|
:host([size="sm"]) .subtitle {
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:host([size="lg"]) .subtitle {
|
||||||
|
font-size: 15px;
|
||||||
|
margin-top: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
color: ${cssManager.bdTheme('#333', '#ccc')};
|
color: ${cssManager.bdTheme('hsl(215.3 25% 26.7%)', 'hsl(217.9 10.6% 84.9%)')};
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Content size variations */
|
||||||
|
:host([size="sm"]) .content {
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:host([size="lg"]) .content {
|
||||||
|
font-size: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Remove margins from first and last children */
|
/* Remove margins from first and last children */
|
||||||
@ -62,16 +143,57 @@ export class DeesPanel extends DeesElement {
|
|||||||
.content ::slotted(*:last-child) {
|
.content ::slotted(*:last-child) {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Interactive states for default variant */
|
||||||
|
:host([variant="default"]:hover) {
|
||||||
|
border-color: ${cssManager.bdTheme('hsl(0 0% 79.8%)', 'hsl(0 0% 20.9%)')};
|
||||||
|
box-shadow: 0 4px 6px -1px hsl(0 0% 0% / 0.1), 0 2px 4px -2px hsl(0 0% 0% / 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Interactive states for outline variant */
|
||||||
|
:host([variant="outline"]:hover) {
|
||||||
|
border-color: ${cssManager.bdTheme('hsl(0 0% 79.8%)', 'hsl(0 0% 20.9%)')};
|
||||||
|
background: ${cssManager.bdTheme('hsl(0 0% 98%)', 'hsl(0 0% 7.8%)')};
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Interactive states for ghost variant */
|
||||||
|
:host([variant="ghost"]:hover) {
|
||||||
|
background: ${cssManager.bdTheme('hsl(0 0% 95.1%)', 'hsl(0 0% 14.9%)')};
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Focus states */
|
||||||
|
:host(:focus-within) {
|
||||||
|
outline: none;
|
||||||
|
border-color: ${cssManager.bdTheme('hsl(222.2 47.4% 51.2%)', 'hsl(217.2 91.2% 59.8%)')};
|
||||||
|
box-shadow: 0 0 0 3px ${cssManager.bdTheme('hsl(222.2 47.4% 51.2% / 0.1)', 'hsl(217.2 91.2% 59.8% / 0.1)')};
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Nested panels spacing */
|
||||||
|
::slotted(dees-panel) {
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::slotted(dees-panel:first-child) {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
|
|
||||||
public render(): TemplateResult {
|
public render(): TemplateResult {
|
||||||
return html`
|
return html`
|
||||||
|
<div class="header">
|
||||||
${this.title ? html`<h3 class="title">${this.title}</h3>` : ''}
|
${this.title ? html`<h3 class="title">${this.title}</h3>` : ''}
|
||||||
${this.subtitle ? html`<p class="subtitle">${this.subtitle}</p>` : ''}
|
${this.subtitle ? html`<p class="subtitle">${this.subtitle}</p>` : ''}
|
||||||
|
</div>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async firstUpdated() {
|
||||||
|
if (this.runAfterRender) {
|
||||||
|
await this.runAfterRender(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
332
ts_web/elements/dees-shopping-productcard.demo.ts
Normal file
332
ts_web/elements/dees-shopping-productcard.demo.ts
Normal file
@ -0,0 +1,332 @@
|
|||||||
|
import { html, css, cssManager } from '@design.estate/dees-element';
|
||||||
|
import '@design.estate/dees-wcctools/demotools';
|
||||||
|
import './dees-panel.js';
|
||||||
|
import type { DeesShoppingProductcard } from './dees-shopping-productcard.js';
|
||||||
|
|
||||||
|
export const demoFunc = () => html`
|
||||||
|
<dees-demowrapper>
|
||||||
|
<style>
|
||||||
|
${css`
|
||||||
|
.demo-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 24px;
|
||||||
|
padding: 24px;
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cart-summary {
|
||||||
|
margin-top: 24px;
|
||||||
|
padding: 20px;
|
||||||
|
background: ${cssManager.bdTheme('hsl(210 40% 96.1%)', 'hsl(215 20.2% 16.8%)')};
|
||||||
|
border: 1px solid ${cssManager.bdTheme('hsl(214.3 31.8% 91.4%)', 'hsl(215 20.2% 21.8%)')};
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cart-summary-title {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
color: ${cssManager.bdTheme('hsl(0 0% 9%)', 'hsl(0 0% 95%)')};
|
||||||
|
}
|
||||||
|
|
||||||
|
.cart-item {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 8px 0;
|
||||||
|
font-size: 14px;
|
||||||
|
color: ${cssManager.bdTheme('hsl(215.3 25% 26.7%)', 'hsl(217.9 10.6% 74.9%)')};
|
||||||
|
}
|
||||||
|
|
||||||
|
.cart-total {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding-top: 16px;
|
||||||
|
margin-top: 16px;
|
||||||
|
border-top: 2px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')};
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: ${cssManager.bdTheme('hsl(0 0% 9%)', 'hsl(0 0% 95%)')};
|
||||||
|
}
|
||||||
|
|
||||||
|
.selected-products {
|
||||||
|
padding: 16px;
|
||||||
|
background: ${cssManager.bdTheme('hsl(210 40% 96.1%)', 'hsl(215 20.2% 16.8%)')};
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 14px;
|
||||||
|
color: ${cssManager.bdTheme('hsl(215.3 25% 26.7%)', 'hsl(217.9 10.6% 74.9%)')};
|
||||||
|
}
|
||||||
|
`}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<div class="demo-container">
|
||||||
|
<dees-panel .title=${'Basic Product Cards'} .subtitle=${'Simple product display with various configurations'}>
|
||||||
|
<div class="product-grid">
|
||||||
|
<dees-shopping-productcard
|
||||||
|
.productData=${{
|
||||||
|
name: 'Wireless Bluetooth Headphones',
|
||||||
|
category: 'Audio',
|
||||||
|
description: 'Premium sound quality with active noise cancellation',
|
||||||
|
price: 149.99,
|
||||||
|
originalPrice: 199.99,
|
||||||
|
iconName: 'lucide:headphones'
|
||||||
|
}}
|
||||||
|
.quantity=${1}
|
||||||
|
></dees-shopping-productcard>
|
||||||
|
|
||||||
|
<dees-shopping-productcard
|
||||||
|
.productData=${{
|
||||||
|
name: 'Smart Watch Series 7',
|
||||||
|
category: 'Wearables',
|
||||||
|
description: 'Track your fitness and stay connected on the go',
|
||||||
|
price: 399.00,
|
||||||
|
iconName: 'lucide:watch'
|
||||||
|
}}
|
||||||
|
.quantity=${1}
|
||||||
|
></dees-shopping-productcard>
|
||||||
|
|
||||||
|
<dees-shopping-productcard
|
||||||
|
.productData=${{
|
||||||
|
name: 'USB-C Hub',
|
||||||
|
category: 'Accessories',
|
||||||
|
price: 49.99,
|
||||||
|
iconName: 'lucide:usb',
|
||||||
|
inStock: false
|
||||||
|
}}
|
||||||
|
.quantity=${0}
|
||||||
|
></dees-shopping-productcard>
|
||||||
|
</div>
|
||||||
|
</dees-panel>
|
||||||
|
|
||||||
|
<dees-panel .title=${'Interactive Shopping Cart'} .subtitle=${'Product cards with dynamic cart calculation'} .runAfterRender=${async (elementArg: HTMLElement) => {
|
||||||
|
const products = [
|
||||||
|
{ id: 'laptop', element: null, data: { name: 'MacBook Pro 14"', category: 'Computers', description: 'M3 Pro chip with 18GB RAM', price: 1999, originalPrice: 2199, iconName: 'lucide:laptop' }},
|
||||||
|
{ id: 'ipad', element: null, data: { name: 'iPad Air', category: 'Tablets', description: '10.9" Liquid Retina display', price: 599, iconName: 'lucide:tablet' }},
|
||||||
|
{ id: 'keyboard', element: null, data: { name: 'Magic Keyboard', category: 'Accessories', description: 'Wireless keyboard with Touch ID', price: 149, iconName: 'lucide:keyboard' }}
|
||||||
|
];
|
||||||
|
|
||||||
|
const updateCartSummary = () => {
|
||||||
|
let total = 0;
|
||||||
|
const items = [];
|
||||||
|
|
||||||
|
products.forEach(product => {
|
||||||
|
const element = elementArg.querySelector(`#${product.id}`) as DeesShoppingProductcard;
|
||||||
|
if (element && element.quantity > 0) {
|
||||||
|
const subtotal = product.data.price * element.quantity;
|
||||||
|
total += subtotal;
|
||||||
|
items.push(`
|
||||||
|
<div class="cart-item">
|
||||||
|
<span>${product.data.name} (${element.quantity})</span>
|
||||||
|
<span>$${subtotal.toFixed(2)}</span>
|
||||||
|
</div>
|
||||||
|
`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const summary = elementArg.querySelector('#interactive-cart-summary');
|
||||||
|
if (summary) {
|
||||||
|
summary.innerHTML = `
|
||||||
|
${items.join('')}
|
||||||
|
${items.length === 0 ? '<div class="cart-item" style="text-align: center; color: #999;">Your cart is empty</div>' : ''}
|
||||||
|
<div class="cart-total">
|
||||||
|
<span>Total</span>
|
||||||
|
<span>$${total.toFixed(2)}</span>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Initial update
|
||||||
|
setTimeout(updateCartSummary, 100);
|
||||||
|
|
||||||
|
// Set up listeners
|
||||||
|
elementArg.querySelectorAll('dees-shopping-productcard').forEach(card => {
|
||||||
|
card.addEventListener('quantityChange', updateCartSummary);
|
||||||
|
});
|
||||||
|
}}>
|
||||||
|
<div class="product-grid">
|
||||||
|
<dees-shopping-productcard
|
||||||
|
id="laptop"
|
||||||
|
.productData=${{
|
||||||
|
name: 'MacBook Pro 14"',
|
||||||
|
category: 'Computers',
|
||||||
|
description: 'M3 Pro chip with 18GB RAM',
|
||||||
|
price: 1999,
|
||||||
|
originalPrice: 2199,
|
||||||
|
iconName: 'lucide:laptop'
|
||||||
|
}}
|
||||||
|
.quantity=${1}
|
||||||
|
></dees-shopping-productcard>
|
||||||
|
|
||||||
|
<dees-shopping-productcard
|
||||||
|
id="ipad"
|
||||||
|
.productData=${{
|
||||||
|
name: 'iPad Air',
|
||||||
|
category: 'Tablets',
|
||||||
|
description: '10.9" Liquid Retina display',
|
||||||
|
price: 599,
|
||||||
|
iconName: 'lucide:tablet'
|
||||||
|
}}
|
||||||
|
.quantity=${0}
|
||||||
|
></dees-shopping-productcard>
|
||||||
|
|
||||||
|
<dees-shopping-productcard
|
||||||
|
id="keyboard"
|
||||||
|
.productData=${{
|
||||||
|
name: 'Magic Keyboard',
|
||||||
|
category: 'Accessories',
|
||||||
|
description: 'Wireless keyboard with Touch ID',
|
||||||
|
price: 149,
|
||||||
|
iconName: 'lucide:keyboard'
|
||||||
|
}}
|
||||||
|
.quantity=${2}
|
||||||
|
></dees-shopping-productcard>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="cart-summary">
|
||||||
|
<h3 class="cart-summary-title">Shopping Cart</h3>
|
||||||
|
<div id="interactive-cart-summary">
|
||||||
|
<!-- Dynamically updated -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</dees-panel>
|
||||||
|
|
||||||
|
<dees-panel .title=${'Selectable Product Cards'} .subtitle=${'Click cards or checkboxes to select products'}>
|
||||||
|
<div class="product-grid">
|
||||||
|
<dees-shopping-productcard
|
||||||
|
.productData=${{
|
||||||
|
name: 'Sony Alpha 7 IV',
|
||||||
|
category: 'Cameras',
|
||||||
|
description: 'Full-frame mirrorless camera',
|
||||||
|
price: 2498,
|
||||||
|
iconName: 'lucide:camera'
|
||||||
|
}}
|
||||||
|
.selectable=${true}
|
||||||
|
.showQuantitySelector=${false}
|
||||||
|
@selectionChange=${(e: CustomEvent) => {
|
||||||
|
const output = document.querySelector('#selection-output');
|
||||||
|
if (output) {
|
||||||
|
const selectedCards = document.querySelectorAll('dees-shopping-productcard[selectable]');
|
||||||
|
const selectedProducts = [];
|
||||||
|
selectedCards.forEach((card: DeesShoppingProductcard) => {
|
||||||
|
if (card.selected) {
|
||||||
|
selectedProducts.push(card.productData.name);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
output.textContent = selectedProducts.length > 0
|
||||||
|
? `Selected: ${selectedProducts.join(', ')}`
|
||||||
|
: 'No products selected';
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
></dees-shopping-productcard>
|
||||||
|
|
||||||
|
<dees-shopping-productcard
|
||||||
|
.productData=${{
|
||||||
|
name: 'DJI Mini 3 Pro',
|
||||||
|
category: 'Drones',
|
||||||
|
description: 'Lightweight drone with 4K camera',
|
||||||
|
price: 759,
|
||||||
|
iconName: 'lucide:plane'
|
||||||
|
}}
|
||||||
|
.selectable=${true}
|
||||||
|
.showQuantitySelector=${false}
|
||||||
|
@selectionChange=${(e: CustomEvent) => {
|
||||||
|
const output = document.querySelector('#selection-output');
|
||||||
|
if (output) {
|
||||||
|
const selectedCards = document.querySelectorAll('dees-shopping-productcard[selectable]');
|
||||||
|
const selectedProducts = [];
|
||||||
|
selectedCards.forEach((card: DeesShoppingProductcard) => {
|
||||||
|
if (card.selected) {
|
||||||
|
selectedProducts.push(card.productData.name);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
output.textContent = selectedProducts.length > 0
|
||||||
|
? `Selected: ${selectedProducts.join(', ')}`
|
||||||
|
: 'No products selected';
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
></dees-shopping-productcard>
|
||||||
|
|
||||||
|
<dees-shopping-productcard
|
||||||
|
.productData=${{
|
||||||
|
name: 'GoPro HERO12',
|
||||||
|
category: 'Action Cameras',
|
||||||
|
description: '5.3K video with HyperSmooth 6.0',
|
||||||
|
price: 399,
|
||||||
|
originalPrice: 449,
|
||||||
|
iconName: 'lucide:video'
|
||||||
|
}}
|
||||||
|
.selectable=${true}
|
||||||
|
.showQuantitySelector=${false}
|
||||||
|
@selectionChange=${(e: CustomEvent) => {
|
||||||
|
const output = document.querySelector('#selection-output');
|
||||||
|
if (output) {
|
||||||
|
const selectedCards = document.querySelectorAll('dees-shopping-productcard[selectable]');
|
||||||
|
const selectedProducts = [];
|
||||||
|
selectedCards.forEach((card: DeesShoppingProductcard) => {
|
||||||
|
if (card.selected) {
|
||||||
|
selectedProducts.push(card.productData.name);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
output.textContent = selectedProducts.length > 0
|
||||||
|
? `Selected: ${selectedProducts.join(', ')}`
|
||||||
|
: 'No products selected';
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
></dees-shopping-productcard>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="selected-products" id="selection-output" style="margin-top: 16px;">
|
||||||
|
No products selected
|
||||||
|
</div>
|
||||||
|
</dees-panel>
|
||||||
|
|
||||||
|
<dees-panel .title=${'Product Variations'} .subtitle=${'Different states and configurations'}>
|
||||||
|
<div class="product-grid">
|
||||||
|
<dees-shopping-productcard
|
||||||
|
.productData=${{
|
||||||
|
name: 'Limited Edition Sneakers',
|
||||||
|
category: 'Footwear',
|
||||||
|
description: 'Exclusive colorway - Only 500 pairs',
|
||||||
|
price: 299,
|
||||||
|
iconName: 'lucide:footprints',
|
||||||
|
inStock: false,
|
||||||
|
stockText: 'Sold Out'
|
||||||
|
}}
|
||||||
|
.quantity=${0}
|
||||||
|
></dees-shopping-productcard>
|
||||||
|
|
||||||
|
<dees-shopping-productcard
|
||||||
|
.productData=${{
|
||||||
|
name: 'Minimalist Wallet',
|
||||||
|
price: 39.99,
|
||||||
|
iconName: 'lucide:wallet'
|
||||||
|
}}
|
||||||
|
.quantity=${1}
|
||||||
|
></dees-shopping-productcard>
|
||||||
|
|
||||||
|
<dees-shopping-productcard
|
||||||
|
.productData=${{
|
||||||
|
name: 'Premium Coffee Beans',
|
||||||
|
category: 'Food & Beverage',
|
||||||
|
description: 'Single origin, medium roast',
|
||||||
|
price: 18.50,
|
||||||
|
iconName: 'lucide:coffee',
|
||||||
|
currency: '€'
|
||||||
|
}}
|
||||||
|
.quantity=${2}
|
||||||
|
></dees-shopping-productcard>
|
||||||
|
</div>
|
||||||
|
</dees-panel>
|
||||||
|
</div>
|
||||||
|
</dees-demowrapper>
|
||||||
|
`;
|
335
ts_web/elements/dees-shopping-productcard.ts
Normal file
335
ts_web/elements/dees-shopping-productcard.ts
Normal file
@ -0,0 +1,335 @@
|
|||||||
|
import {
|
||||||
|
customElement,
|
||||||
|
property,
|
||||||
|
html,
|
||||||
|
css,
|
||||||
|
cssManager,
|
||||||
|
type TemplateResult,
|
||||||
|
DeesElement,
|
||||||
|
} from '@design.estate/dees-element';
|
||||||
|
import { demoFunc } from './dees-shopping-productcard.demo.js';
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
'dees-shopping-productcard': DeesShoppingProductcard;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IProductData {
|
||||||
|
name: string;
|
||||||
|
category?: string;
|
||||||
|
description?: string;
|
||||||
|
price: number;
|
||||||
|
originalPrice?: number;
|
||||||
|
currency?: string;
|
||||||
|
inStock?: boolean;
|
||||||
|
stockText?: string;
|
||||||
|
imageUrl?: string;
|
||||||
|
iconName?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
@customElement('dees-shopping-productcard')
|
||||||
|
export class DeesShoppingProductcard extends DeesElement {
|
||||||
|
public static demo = demoFunc;
|
||||||
|
|
||||||
|
@property({ type: Object })
|
||||||
|
public productData: IProductData = {
|
||||||
|
name: 'Product Name',
|
||||||
|
price: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
@property({ type: Number })
|
||||||
|
public quantity: number = 0;
|
||||||
|
|
||||||
|
@property({ type: Boolean })
|
||||||
|
public showQuantitySelector: boolean = true;
|
||||||
|
|
||||||
|
@property({ type: Boolean })
|
||||||
|
public selectable: boolean = false;
|
||||||
|
|
||||||
|
@property({ type: Boolean })
|
||||||
|
public selected: boolean = false;
|
||||||
|
|
||||||
|
public static styles = [
|
||||||
|
cssManager.defaultStyles,
|
||||||
|
css`
|
||||||
|
:host {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-card {
|
||||||
|
background: ${cssManager.bdTheme('hsl(0 0% 100%)', 'hsl(215 20.2% 11.8%)')};
|
||||||
|
border: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')};
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-card:hover {
|
||||||
|
border-color: ${cssManager.bdTheme('hsl(0 0% 79.8%)', 'hsl(0 0% 20.9%)')};
|
||||||
|
box-shadow: 0 4px 6px -1px hsl(0 0% 0% / 0.1), 0 2px 4px -2px hsl(0 0% 0% / 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-card.selectable {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-card.selected {
|
||||||
|
border-color: ${cssManager.bdTheme('hsl(217.2 91.2% 59.8%)', 'hsl(213.1 93.9% 67.8%)')};
|
||||||
|
box-shadow: 0 0 0 3px ${cssManager.bdTheme('hsl(217.2 91.2% 59.8% / 0.1)', 'hsl(213.1 93.9% 67.8% / 0.1)')};
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-image {
|
||||||
|
width: 100%;
|
||||||
|
height: 180px;
|
||||||
|
background: ${cssManager.bdTheme('hsl(210 40% 96.1%)', 'hsl(215 20.2% 16.8%)')};
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-image img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-image dees-icon {
|
||||||
|
font-size: 48px;
|
||||||
|
color: ${cssManager.bdTheme('hsl(215 20.2% 65.1%)', 'hsl(215 20.2% 35.1%)')};
|
||||||
|
}
|
||||||
|
|
||||||
|
.selection-checkbox {
|
||||||
|
position: absolute;
|
||||||
|
top: 12px;
|
||||||
|
right: 12px;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
background: ${cssManager.bdTheme('hsl(0 0% 100%)', 'hsl(215 30% 6.8%)')};
|
||||||
|
border: 2px solid ${cssManager.bdTheme('hsl(215 20.2% 65.1%)', 'hsl(215 20.2% 35.1%)')};
|
||||||
|
border-radius: 4px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selection-checkbox.checked {
|
||||||
|
background: ${cssManager.bdTheme('hsl(217.2 91.2% 59.8%)', 'hsl(213.1 93.9% 67.8%)')};
|
||||||
|
border-color: ${cssManager.bdTheme('hsl(217.2 91.2% 59.8%)', 'hsl(213.1 93.9% 67.8%)')};
|
||||||
|
}
|
||||||
|
|
||||||
|
.selection-checkbox dees-icon {
|
||||||
|
color: white;
|
||||||
|
font-size: 12px;
|
||||||
|
opacity: 0;
|
||||||
|
transform: scale(0);
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selection-checkbox.checked dees-icon {
|
||||||
|
opacity: 1;
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-content {
|
||||||
|
padding: 16px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-header {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-category {
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: ${cssManager.bdTheme('hsl(215.4 16.3% 56.9%)', 'hsl(215 20.2% 55.1%)')};
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.05em;
|
||||||
|
line-height: 1.3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-name {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: ${cssManager.bdTheme('hsl(0 0% 9%)', 'hsl(0 0% 95%)')};
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-description {
|
||||||
|
font-size: 13px;
|
||||||
|
color: ${cssManager.bdTheme('hsl(215.4 16.3% 46.9%)', 'hsl(215 20.2% 65.1%)')};
|
||||||
|
line-height: 1.5;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-footer {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 16px;
|
||||||
|
padding-top: 12px;
|
||||||
|
border-top: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')};
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-price {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.price-current {
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: ${cssManager.bdTheme('hsl(0 0% 9%)', 'hsl(0 0% 95%)')};
|
||||||
|
}
|
||||||
|
|
||||||
|
.price-original {
|
||||||
|
font-size: 14px;
|
||||||
|
color: ${cssManager.bdTheme('hsl(215.4 16.3% 56.9%)', 'hsl(215 20.2% 55.1%)')};
|
||||||
|
text-decoration: line-through;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stock-status {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
font-size: 12px;
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stock-status.in-stock {
|
||||||
|
color: ${cssManager.bdTheme('hsl(142.1 70.6% 45.3%)', 'hsl(142.1 76.2% 36.3%)')};
|
||||||
|
}
|
||||||
|
|
||||||
|
.stock-status.out-of-stock {
|
||||||
|
color: ${cssManager.bdTheme('hsl(0 72.2% 50.6%)', 'hsl(0 62.8% 30.6%)')};
|
||||||
|
}
|
||||||
|
|
||||||
|
.stock-status dees-icon {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
];
|
||||||
|
|
||||||
|
public render(): TemplateResult {
|
||||||
|
const {
|
||||||
|
name,
|
||||||
|
category,
|
||||||
|
description,
|
||||||
|
price,
|
||||||
|
originalPrice,
|
||||||
|
currency = '$',
|
||||||
|
inStock = true,
|
||||||
|
stockText = inStock ? 'In Stock' : 'Out of Stock',
|
||||||
|
imageUrl,
|
||||||
|
iconName = 'lucide:package',
|
||||||
|
} = this.productData;
|
||||||
|
|
||||||
|
const formatPrice = (value: number) => {
|
||||||
|
return `${currency}${value.toFixed(2)}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<div
|
||||||
|
class="product-card ${this.selectable ? 'selectable' : ''} ${this.selected ? 'selected' : ''}"
|
||||||
|
@click=${this.handleCardClick}
|
||||||
|
>
|
||||||
|
<div class="product-image">
|
||||||
|
${imageUrl ? html`
|
||||||
|
<img src="${imageUrl}" alt="${name}">
|
||||||
|
` : html`
|
||||||
|
<dees-icon .iconName=${iconName}></dees-icon>
|
||||||
|
`}
|
||||||
|
${this.selectable ? html`
|
||||||
|
<div
|
||||||
|
class="selection-checkbox ${this.selected ? 'checked' : ''}"
|
||||||
|
@click=${(e: Event) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
this.handleSelectionToggle();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<dees-icon .iconName=${'lucide:check'}></dees-icon>
|
||||||
|
</div>
|
||||||
|
` : ''}
|
||||||
|
</div>
|
||||||
|
<div class="product-content">
|
||||||
|
<div class="product-header">
|
||||||
|
${category ? html`<div class="product-category">${category}</div>` : ''}
|
||||||
|
<div class="product-name">${name}</div>
|
||||||
|
</div>
|
||||||
|
${description ? html`
|
||||||
|
<div class="product-description">${description}</div>
|
||||||
|
` : ''}
|
||||||
|
<div class="stock-status ${inStock ? 'in-stock' : 'out-of-stock'}">
|
||||||
|
<dees-icon .iconName=${inStock ? 'lucide:check-circle' : 'lucide:x-circle'}></dees-icon>
|
||||||
|
${stockText}
|
||||||
|
</div>
|
||||||
|
<div class="product-footer">
|
||||||
|
<div class="product-price">
|
||||||
|
<span class="price-current">${formatPrice(price)}</span>
|
||||||
|
${originalPrice && originalPrice > price ? html`
|
||||||
|
<span class="price-original">${formatPrice(originalPrice)}</span>
|
||||||
|
` : ''}
|
||||||
|
</div>
|
||||||
|
${this.showQuantitySelector ? html`
|
||||||
|
<dees-input-quantityselector
|
||||||
|
.value=${this.quantity}
|
||||||
|
@changeSubject=${(e: CustomEvent) => {
|
||||||
|
this.quantity = e.detail.getValue();
|
||||||
|
this.dispatchEvent(new CustomEvent('quantityChange', {
|
||||||
|
detail: {
|
||||||
|
quantity: this.quantity,
|
||||||
|
productData: this.productData
|
||||||
|
},
|
||||||
|
bubbles: true,
|
||||||
|
composed: true
|
||||||
|
}));
|
||||||
|
}}
|
||||||
|
></dees-input-quantityselector>
|
||||||
|
` : ''}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleCardClick() {
|
||||||
|
if (this.selectable) {
|
||||||
|
this.selected = !this.selected;
|
||||||
|
this.dispatchEvent(new CustomEvent('selectionChange', {
|
||||||
|
detail: {
|
||||||
|
selected: this.selected,
|
||||||
|
productData: this.productData
|
||||||
|
},
|
||||||
|
bubbles: true,
|
||||||
|
composed: true
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleSelectionToggle() {
|
||||||
|
this.selected = !this.selected;
|
||||||
|
this.dispatchEvent(new CustomEvent('selectionChange', {
|
||||||
|
detail: {
|
||||||
|
selected: this.selected,
|
||||||
|
productData: this.productData
|
||||||
|
},
|
||||||
|
bubbles: true,
|
||||||
|
composed: true
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
@ -1,34 +1,99 @@
|
|||||||
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';
|
import type { IStatsTile } from './dees-statsgrid.js';
|
||||||
|
|
||||||
export const demoFunc = () => {
|
export const demoFunc = () => {
|
||||||
// Demo data with different tile types
|
return html`
|
||||||
const demoTiles: IStatsTile[] = [
|
<dees-demowrapper>
|
||||||
|
<style>
|
||||||
|
${css`
|
||||||
|
.demo-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 24px;
|
||||||
|
padding: 24px;
|
||||||
|
max-width: 1400px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
dees-panel {
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
dees-panel:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tile-config {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||||
|
gap: 16px;
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-section {
|
||||||
|
padding: 16px;
|
||||||
|
background: ${cssManager.bdTheme('hsl(210 40% 96.1%)', 'hsl(215 20.2% 16.8%)')};
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-title {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
color: ${cssManager.bdTheme('hsl(215.3 25% 8.8%)', 'hsl(210 40% 98%)')};
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-description {
|
||||||
|
font-size: 13px;
|
||||||
|
color: ${cssManager.bdTheme('hsl(215.4 16.3% 56.9%)', 'hsl(215 20.2% 55.1%)')};
|
||||||
|
}
|
||||||
|
|
||||||
|
.code-block {
|
||||||
|
background: ${cssManager.bdTheme('hsl(210 40% 96.1%)', 'hsl(215 20.2% 11.8%)')};
|
||||||
|
border: 1px solid ${cssManager.bdTheme('hsl(214.3 31.8% 91.4%)', 'hsl(215 20.2% 16.8%)')};
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 16px;
|
||||||
|
font-family: monospace;
|
||||||
|
font-size: 13px;
|
||||||
|
overflow-x: auto;
|
||||||
|
white-space: pre;
|
||||||
|
}
|
||||||
|
`}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<div class="demo-container">
|
||||||
|
<dees-panel .title=${'1. Comprehensive Dashboard'} .subtitle=${'Full-featured stats grid with various tile types, actions, and Lucide icons'}>
|
||||||
|
<dees-statsgrid
|
||||||
|
.tiles=${[
|
||||||
{
|
{
|
||||||
id: 'revenue',
|
id: 'revenue',
|
||||||
title: 'Total Revenue',
|
title: 'Total Revenue',
|
||||||
value: 125420,
|
value: 125420,
|
||||||
unit: '$',
|
unit: '$',
|
||||||
type: 'number',
|
type: 'number',
|
||||||
icon: 'faDollarSign',
|
icon: 'lucide:dollar-sign',
|
||||||
description: '+12.5% from last month',
|
description: '+12.5% from last month',
|
||||||
color: '#22c55e',
|
|
||||||
actions: [
|
actions: [
|
||||||
{
|
{
|
||||||
name: 'View Details',
|
name: 'View Details',
|
||||||
iconName: 'faChartLine',
|
iconName: 'lucide:trending-up',
|
||||||
action: async () => {
|
action: async () => {
|
||||||
console.log('Viewing revenue details for tile:', 'revenue');
|
const output = document.querySelector('#action-output');
|
||||||
console.log('Current value:', 125420);
|
if (output) {
|
||||||
alert(`Revenue Details: $125,420 (+12.5%)`);
|
output.textContent = 'Viewing revenue details: $125,420 (+12.5%)';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Export Data',
|
name: 'Export Data',
|
||||||
iconName: 'faFileExport',
|
iconName: 'lucide:download',
|
||||||
action: async () => {
|
action: async () => {
|
||||||
console.log('Exporting revenue data');
|
const output = document.querySelector('#action-output');
|
||||||
alert('Revenue data exported to CSV');
|
if (output) {
|
||||||
|
output.textContent = 'Exporting revenue data to CSV...';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@ -38,14 +103,17 @@ export const demoFunc = () => {
|
|||||||
title: 'Active Users',
|
title: 'Active Users',
|
||||||
value: 3847,
|
value: 3847,
|
||||||
type: 'number',
|
type: 'number',
|
||||||
icon: 'faUsers',
|
icon: 'lucide:users',
|
||||||
description: '324 new this week',
|
description: '324 new this week',
|
||||||
actions: [
|
actions: [
|
||||||
{
|
{
|
||||||
name: 'View User List',
|
name: 'View User List',
|
||||||
iconName: 'faList',
|
iconName: 'lucide:list',
|
||||||
action: async () => {
|
action: async () => {
|
||||||
console.log('Viewing user list');
|
const output = document.querySelector('#action-output');
|
||||||
|
if (output) {
|
||||||
|
output.textContent = 'Opening user list...';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@ -54,15 +122,16 @@ export const demoFunc = () => {
|
|||||||
id: 'cpu',
|
id: 'cpu',
|
||||||
title: 'CPU Usage',
|
title: 'CPU Usage',
|
||||||
value: 73,
|
value: 73,
|
||||||
|
unit: '%',
|
||||||
type: 'gauge',
|
type: 'gauge',
|
||||||
icon: 'faMicrochip',
|
icon: 'lucide:cpu',
|
||||||
gaugeOptions: {
|
gaugeOptions: {
|
||||||
min: 0,
|
min: 0,
|
||||||
max: 100,
|
max: 100,
|
||||||
thresholds: [
|
thresholds: [
|
||||||
{ value: 0, color: '#22c55e' },
|
{ value: 0, color: 'hsl(142.1 76.2% 36.3%)' },
|
||||||
{ value: 60, color: '#f59e0b' },
|
{ value: 60, color: 'hsl(45.4 93.4% 47.5%)' },
|
||||||
{ value: 80, color: '#ef4444' }
|
{ value: 80, color: 'hsl(0 84.2% 60.2%)' }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -71,43 +140,8 @@ export const demoFunc = () => {
|
|||||||
title: 'Storage Used',
|
title: 'Storage Used',
|
||||||
value: 65,
|
value: 65,
|
||||||
type: 'percentage',
|
type: 'percentage',
|
||||||
icon: 'faHardDrive',
|
icon: 'lucide:hard-drive',
|
||||||
description: '650 GB of 1 TB',
|
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',
|
id: 'latency',
|
||||||
@ -115,275 +149,370 @@ export const demoFunc = () => {
|
|||||||
value: 142,
|
value: 142,
|
||||||
unit: 'ms',
|
unit: 'ms',
|
||||||
type: 'trend',
|
type: 'trend',
|
||||||
icon: 'faClock',
|
icon: 'lucide:activity',
|
||||||
trendData: [150, 145, 148, 142, 138, 140, 135, 145, 142],
|
trendData: [150, 145, 148, 142, 138, 140, 135, 145, 142],
|
||||||
description: 'P95 latency'
|
description: 'P95'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'errors',
|
id: 'uptime',
|
||||||
title: 'Error Rate',
|
title: 'System Uptime',
|
||||||
value: 0.03,
|
value: '99.95%',
|
||||||
unit: '%',
|
type: 'text',
|
||||||
type: 'number',
|
icon: 'lucide:check-circle',
|
||||||
icon: 'faExclamationTriangle',
|
color: 'hsl(142.1 76.2% 36.3%)',
|
||||||
color: '#ef4444',
|
description: 'Last 30 days'
|
||||||
actions: [
|
|
||||||
{
|
|
||||||
name: 'View Error Logs',
|
|
||||||
iconName: 'faFileAlt',
|
|
||||||
action: async () => {
|
|
||||||
console.log('Viewing error logs');
|
|
||||||
}
|
}
|
||||||
}
|
]}
|
||||||
]
|
.gridActions=${[
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
// Grid actions for the demo
|
|
||||||
const gridActions = [
|
|
||||||
{
|
{
|
||||||
name: 'Refresh',
|
name: 'Refresh',
|
||||||
iconName: 'faSync',
|
iconName: 'lucide:refresh-cw',
|
||||||
action: async () => {
|
action: async () => {
|
||||||
console.log('Refreshing stats...');
|
|
||||||
// Simulate refresh animation
|
|
||||||
const grid = document.querySelector('dees-statsgrid');
|
const grid = document.querySelector('dees-statsgrid');
|
||||||
if (grid) {
|
if (grid) {
|
||||||
grid.style.opacity = '0.5';
|
grid.style.opacity = '0.5';
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
grid.style.opacity = '1';
|
grid.style.opacity = '1';
|
||||||
}, 500);
|
}, 300);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Export Report',
|
name: 'Export',
|
||||||
iconName: 'faFileExport',
|
iconName: 'lucide:share',
|
||||||
action: async () => {
|
action: async () => {
|
||||||
console.log('Exporting stats report...');
|
const output = document.querySelector('#action-output');
|
||||||
|
if (output) {
|
||||||
|
output.textContent = 'Exporting dashboard report...';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Settings',
|
name: 'Settings',
|
||||||
iconName: 'faCog',
|
iconName: 'lucide:settings',
|
||||||
action: async () => {
|
action: async () => {
|
||||||
console.log('Opening settings...');
|
const output = document.querySelector('#action-output');
|
||||||
|
if (output) {
|
||||||
|
output.textContent = 'Opening dashboard settings...';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
];
|
|
||||||
|
|
||||||
return html`
|
|
||||||
<style>
|
|
||||||
.demo-container {
|
|
||||||
padding: 32px;
|
|
||||||
background: ${cssManager.bdTheme('#f8f9fa', '#0a0a0a')};
|
|
||||||
min-height: 100vh;
|
|
||||||
}
|
}
|
||||||
|
]}
|
||||||
.demo-section {
|
|
||||||
margin-bottom: 48px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.demo-title {
|
|
||||||
font-size: 24px;
|
|
||||||
font-weight: 600;
|
|
||||||
margin-bottom: 16px;
|
|
||||||
color: ${cssManager.bdTheme('#333', '#fff')};
|
|
||||||
}
|
|
||||||
|
|
||||||
.demo-description {
|
|
||||||
font-size: 14px;
|
|
||||||
color: ${cssManager.bdTheme('#666', '#aaa')};
|
|
||||||
margin-bottom: 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.theme-toggle {
|
|
||||||
position: fixed;
|
|
||||||
top: 16px;
|
|
||||||
right: 16px;
|
|
||||||
padding: 8px 16px;
|
|
||||||
background: ${cssManager.bdTheme('#fff', '#1a1a1a')};
|
|
||||||
border: 1px solid ${cssManager.bdTheme('#e0e0e0', '#2a2a2a')};
|
|
||||||
border-radius: 8px;
|
|
||||||
cursor: pointer;
|
|
||||||
z-index: 100;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<div class="demo-container">
|
|
||||||
<button class="theme-toggle" @click=${() => {
|
|
||||||
document.body.classList.toggle('bright');
|
|
||||||
}}>Toggle Theme</button>
|
|
||||||
|
|
||||||
<div class="demo-section">
|
|
||||||
<h2 class="demo-title">Full Featured Stats Grid</h2>
|
|
||||||
<p class="demo-description">
|
|
||||||
A comprehensive dashboard with various tile types, actions, and real-time updates.
|
|
||||||
</p>
|
|
||||||
<dees-statsgrid
|
|
||||||
.tiles=${demoTiles}
|
|
||||||
.gridActions=${gridActions}
|
|
||||||
.minTileWidth=${250}
|
.minTileWidth=${250}
|
||||||
.gap=${16}
|
.gap=${16}
|
||||||
></dees-statsgrid>
|
></dees-statsgrid>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="demo-section">
|
<div id="action-output" style="margin-top: 16px; padding: 12px; background: ${cssManager.bdTheme('hsl(210 40% 96.1%)', 'hsl(215 20.2% 16.8%)')}; border-radius: 6px; font-size: 14px; font-family: monospace; color: ${cssManager.bdTheme('hsl(215.3 25% 8.8%)', 'hsl(210 40% 98%)')};">
|
||||||
<h2 class="demo-title">Compact Grid (Smaller Tiles)</h2>
|
<em>Click on tile actions or grid actions to see the result...</em>
|
||||||
<p class="demo-description">
|
|
||||||
Same data displayed with smaller minimum tile width for more compact layouts.
|
|
||||||
</p>
|
|
||||||
<dees-statsgrid
|
|
||||||
.tiles=${demoTiles.slice(0, 6)}
|
|
||||||
.minTileWidth=${180}
|
|
||||||
.gap=${12}
|
|
||||||
></dees-statsgrid>
|
|
||||||
</div>
|
</div>
|
||||||
|
</dees-panel>
|
||||||
|
|
||||||
<div class="demo-section">
|
<dees-panel .title=${'2. Tile Types'} .subtitle=${'Different visualization types available in the stats grid'}>
|
||||||
<h2 class="demo-title">Simple Metrics (No Actions)</h2>
|
|
||||||
<p class="demo-description">
|
|
||||||
Clean display without interactive elements for pure visualization.
|
|
||||||
</p>
|
|
||||||
<dees-statsgrid
|
<dees-statsgrid
|
||||||
.tiles=${[
|
.tiles=${[
|
||||||
{
|
{
|
||||||
id: 'metric1',
|
id: 'number-example',
|
||||||
title: 'Total Sales',
|
title: 'Number Tile',
|
||||||
value: 48293,
|
value: 42195,
|
||||||
type: 'number',
|
|
||||||
icon: 'faShoppingCart'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'metric2',
|
|
||||||
title: 'Conversion Rate',
|
|
||||||
value: 3.4,
|
|
||||||
unit: '%',
|
|
||||||
type: 'number',
|
|
||||||
icon: 'faChartLine'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'metric3',
|
|
||||||
title: 'Avg Order Value',
|
|
||||||
value: 127.50,
|
|
||||||
unit: '$',
|
unit: '$',
|
||||||
type: 'number',
|
type: 'number',
|
||||||
icon: 'faReceipt'
|
icon: 'lucide:hash',
|
||||||
|
description: 'Simple numeric display'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'metric4',
|
id: 'gauge-example',
|
||||||
title: 'Customer Satisfaction',
|
title: 'Gauge Tile',
|
||||||
value: 92,
|
value: 68,
|
||||||
type: 'percentage',
|
unit: '%',
|
||||||
icon: 'faSmile',
|
|
||||||
color: '#22c55e'
|
|
||||||
}
|
|
||||||
]}
|
|
||||||
.minTileWidth=${220}
|
|
||||||
.gap=${16}
|
|
||||||
></dees-statsgrid>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="demo-section">
|
|
||||||
<h2 class="demo-title">Performance Monitoring</h2>
|
|
||||||
<p class="demo-description">
|
|
||||||
Real-time performance metrics with gauge visualizations and thresholds.
|
|
||||||
</p>
|
|
||||||
<dees-statsgrid
|
|
||||||
.tiles=${[
|
|
||||||
{
|
|
||||||
id: 'perf1',
|
|
||||||
title: 'Database Load',
|
|
||||||
value: 42,
|
|
||||||
type: 'gauge',
|
type: 'gauge',
|
||||||
icon: 'faDatabase',
|
icon: 'lucide:gauge',
|
||||||
gaugeOptions: {
|
gaugeOptions: {
|
||||||
min: 0,
|
min: 0,
|
||||||
max: 100,
|
max: 100,
|
||||||
thresholds: [
|
thresholds: [
|
||||||
{ value: 0, color: '#10b981' },
|
{ value: 0, color: 'hsl(142.1 76.2% 36.3%)' },
|
||||||
{ value: 50, color: '#f59e0b' },
|
{ value: 50, color: 'hsl(45.4 93.4% 47.5%)' },
|
||||||
{ value: 75, color: '#ef4444' }
|
{ value: 80, color: 'hsl(0 84.2% 60.2%)' }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'perf2',
|
id: 'percentage-example',
|
||||||
title: 'Network I/O',
|
title: 'Percentage Tile',
|
||||||
value: 856,
|
value: 78,
|
||||||
unit: 'MB/s',
|
|
||||||
type: 'trend',
|
|
||||||
icon: 'faNetworkWired',
|
|
||||||
trendData: [720, 780, 823, 845, 812, 876, 856]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'perf3',
|
|
||||||
title: 'Cache Hit Rate',
|
|
||||||
value: 94.2,
|
|
||||||
type: 'percentage',
|
type: 'percentage',
|
||||||
icon: 'faBolt',
|
icon: 'lucide:percent',
|
||||||
color: '#3b82f6'
|
description: 'Progress bar visualization'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'perf4',
|
id: 'trend-example',
|
||||||
title: 'Active Connections',
|
title: 'Trend Tile',
|
||||||
value: 1428,
|
value: 892,
|
||||||
|
unit: 'ops/s',
|
||||||
|
type: 'trend',
|
||||||
|
icon: 'lucide:trending-up',
|
||||||
|
trendData: [720, 750, 780, 795, 810, 835, 850, 865, 880, 892],
|
||||||
|
description: 'avg'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'text-example',
|
||||||
|
title: 'Text Tile',
|
||||||
|
value: 'Operational',
|
||||||
|
type: 'text',
|
||||||
|
icon: 'lucide:info',
|
||||||
|
color: 'hsl(142.1 76.2% 36.3%)',
|
||||||
|
description: 'Status display'
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
.minTileWidth=${280}
|
||||||
|
.gap=${16}
|
||||||
|
></dees-statsgrid>
|
||||||
|
|
||||||
|
<div class="tile-config">
|
||||||
|
<div class="config-section">
|
||||||
|
<div class="config-title">Configuration Options</div>
|
||||||
|
<div class="config-description">
|
||||||
|
Each tile type supports different properties:
|
||||||
|
<ul style="margin: 8px 0; padding-left: 20px;">
|
||||||
|
<li><strong>Number:</strong> value, unit, color, description</li>
|
||||||
|
<li><strong>Gauge:</strong> value, unit, gaugeOptions (min, max, thresholds)</li>
|
||||||
|
<li><strong>Percentage:</strong> value (0-100), color, description</li>
|
||||||
|
<li><strong>Trend:</strong> value, unit, trendData array, description</li>
|
||||||
|
<li><strong>Text:</strong> value (string), color, description</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</dees-panel>
|
||||||
|
|
||||||
|
<dees-panel .title=${'3. Grid Configurations'} .subtitle=${'Different layout options and responsive behavior'}>
|
||||||
|
<h4 style="margin: 0 0 16px 0; font-size: 16px; font-weight: 600;">Compact Layout (180px tiles)</h4>
|
||||||
|
<dees-statsgrid
|
||||||
|
.tiles=${[
|
||||||
|
{ id: '1', title: 'Orders', value: 156, type: 'number', icon: 'lucide:shopping-cart' },
|
||||||
|
{ id: '2', title: 'Revenue', value: 8420, unit: '$', type: 'number', icon: 'lucide:dollar-sign' },
|
||||||
|
{ id: '3', title: 'Users', value: 423, type: 'number', icon: 'lucide:users' },
|
||||||
|
{ id: '4', title: 'Growth', value: 12.5, unit: '%', type: 'number', icon: 'lucide:trending-up', color: 'hsl(142.1 76.2% 36.3%)' }
|
||||||
|
]}
|
||||||
|
.minTileWidth=${180}
|
||||||
|
.gap=${12}
|
||||||
|
></dees-statsgrid>
|
||||||
|
|
||||||
|
<h4 style="margin: 24px 0 16px 0; font-size: 16px; font-weight: 600;">Spacious Layout (320px tiles)</h4>
|
||||||
|
<dees-statsgrid
|
||||||
|
.tiles=${[
|
||||||
|
{
|
||||||
|
id: 'spacious1',
|
||||||
|
title: 'Monthly Revenue',
|
||||||
|
value: 184500,
|
||||||
|
unit: '$',
|
||||||
type: 'number',
|
type: 'number',
|
||||||
icon: 'faLink',
|
icon: 'lucide:credit-card',
|
||||||
description: 'Peak: 2,100'
|
description: 'Total revenue this month'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'spacious2',
|
||||||
|
title: 'Customer Satisfaction',
|
||||||
|
value: 94,
|
||||||
|
type: 'percentage',
|
||||||
|
icon: 'lucide:smile',
|
||||||
|
description: 'Based on 1,234 reviews'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'spacious3',
|
||||||
|
title: 'Server Response',
|
||||||
|
value: 98,
|
||||||
|
unit: 'ms',
|
||||||
|
type: 'trend',
|
||||||
|
icon: 'lucide:server',
|
||||||
|
trendData: [105, 102, 100, 99, 98, 98, 97, 98],
|
||||||
|
description: 'avg response time'
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
.minTileWidth=${320}
|
||||||
|
.gap=${20}
|
||||||
|
></dees-statsgrid>
|
||||||
|
</dees-panel>
|
||||||
|
|
||||||
|
<dees-panel .title=${'4. Interactive Features'} .subtitle=${'Tiles with actions and real-time updates'}>
|
||||||
|
<dees-statsgrid
|
||||||
|
id="interactive-grid"
|
||||||
|
.tiles=${[
|
||||||
|
{
|
||||||
|
id: 'live-cpu',
|
||||||
|
title: 'Live CPU',
|
||||||
|
value: 45,
|
||||||
|
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%)' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'live-requests',
|
||||||
|
title: 'Requests/sec',
|
||||||
|
value: 892,
|
||||||
|
type: 'trend',
|
||||||
|
icon: 'lucide:activity',
|
||||||
|
trendData: [850, 860, 870, 880, 885, 890, 892]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'live-memory',
|
||||||
|
title: 'Memory Usage',
|
||||||
|
value: 62,
|
||||||
|
type: 'percentage',
|
||||||
|
icon: 'lucide:database'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'counter',
|
||||||
|
title: 'Event Counter',
|
||||||
|
value: 0,
|
||||||
|
type: 'number',
|
||||||
|
icon: 'lucide:zap',
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
name: 'Increment',
|
||||||
|
iconName: 'lucide:plus',
|
||||||
|
action: async () => {
|
||||||
|
const grid = document.querySelector('#interactive-grid') as any;
|
||||||
|
if (!grid) return;
|
||||||
|
const tile = grid.tiles.find((t: any) => t.id === 'counter');
|
||||||
|
tile.value = typeof tile.value === 'number' ? tile.value + 1 : 1;
|
||||||
|
grid.tiles = [...grid.tiles];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Reset',
|
||||||
|
iconName: 'lucide:rotate-ccw',
|
||||||
|
action: async () => {
|
||||||
|
const grid = document.querySelector('#interactive-grid') as any;
|
||||||
|
if (!grid) return;
|
||||||
|
const tile = grid.tiles.find((t: any) => t.id === 'counter');
|
||||||
|
tile.value = 0;
|
||||||
|
grid.tiles = [...grid.tiles];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
.gridActions=${[
|
.gridActions=${[
|
||||||
{
|
{
|
||||||
name: 'Auto Refresh',
|
name: 'Start Live Updates',
|
||||||
iconName: 'faPlay',
|
iconName: 'lucide:play',
|
||||||
action: async () => {
|
action: async function() {
|
||||||
console.log('Starting auto refresh...');
|
// Toggle live updates
|
||||||
|
if (!(window as any).liveUpdateInterval) {
|
||||||
|
(window as any).liveUpdateInterval = setInterval(() => {
|
||||||
|
const grid = document.querySelector('#interactive-grid') as any;
|
||||||
|
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 as any).liveUpdateInterval);
|
||||||
|
(window as any).liveUpdateInterval = null;
|
||||||
|
this.name = 'Start Live Updates';
|
||||||
|
this.iconName = 'lucide:play';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
.minTileWidth=${280}
|
.minTileWidth=${250}
|
||||||
.gap=${20}
|
.gap=${16}
|
||||||
></dees-statsgrid>
|
></dees-statsgrid>
|
||||||
|
</dees-panel>
|
||||||
|
|
||||||
|
<dees-panel .title=${'5. Code Example'} .subtitle=${'How to implement a stats grid with TypeScript'}>
|
||||||
|
<div class="code-block">${`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\`
|
||||||
|
<dees-statsgrid
|
||||||
|
.tiles=\${tiles}
|
||||||
|
.minTileWidth=\${250}
|
||||||
|
.gap=\${16}
|
||||||
|
.gridActions=\${[
|
||||||
|
{
|
||||||
|
name: 'Refresh',
|
||||||
|
iconName: 'lucide:refresh-cw',
|
||||||
|
action: async () => console.log('Refresh')
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
></dees-statsgrid>
|
||||||
|
\`;`}</div>
|
||||||
|
</dees-panel>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
// Simulate real-time updates
|
// Cleanup live updates on page unload
|
||||||
setInterval(() => {
|
window.addEventListener('beforeunload', () => {
|
||||||
const grids = document.querySelectorAll('dees-statsgrid');
|
if ((window as any).liveUpdateInterval) {
|
||||||
grids.forEach(grid => {
|
clearInterval((window as any).liveUpdateInterval);
|
||||||
if (grid.tiles && grid.tiles.length > 0) {
|
|
||||||
// Update some random values
|
|
||||||
const updatedTiles = [...grid.tiles];
|
|
||||||
|
|
||||||
// Update trends with new data point
|
|
||||||
updatedTiles.forEach(tile => {
|
|
||||||
if (tile.type === 'trend' && tile.trendData) {
|
|
||||||
tile.trendData = [...tile.trendData.slice(1),
|
|
||||||
tile.trendData[tile.trendData.length - 1] + Math.random() * 10 - 5
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Randomly update some numeric values
|
|
||||||
if (tile.type === 'number' && Math.random() > 0.7) {
|
|
||||||
const currentValue = typeof tile.value === 'number' ? tile.value : parseFloat(tile.value);
|
|
||||||
tile.value = Math.round(currentValue + (Math.random() * 10 - 5));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update gauge values
|
|
||||||
if (tile.type === 'gauge' && Math.random() > 0.5) {
|
|
||||||
const currentValue = typeof tile.value === 'number' ? tile.value : parseFloat(tile.value);
|
|
||||||
const newValue = currentValue + (Math.random() * 10 - 5);
|
|
||||||
tile.value = Math.max(tile.gaugeOptions?.min || 0,
|
|
||||||
Math.min(tile.gaugeOptions?.max || 100, Math.round(newValue)));
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
grid.tiles = updatedTiles;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}, 3000);
|
|
||||||
</script>
|
</script>
|
||||||
</div>
|
</dees-demowrapper>
|
||||||
`;
|
`;
|
||||||
};
|
};
|
@ -81,28 +81,44 @@ export class DeesStatsGrid extends DeesElement {
|
|||||||
width: 100%;
|
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 {
|
.grid-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin-bottom: ${unsafeCSS(16)}px;
|
margin-bottom: calc(var(--grid-gap) * 1.5);
|
||||||
min-height: 32px;
|
min-height: 40px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.grid-title {
|
.grid-title {
|
||||||
font-size: 18px;
|
font-size: 16px;
|
||||||
font-weight: 600;
|
font-weight: 500;
|
||||||
color: ${cssManager.bdTheme('#333', '#fff')};
|
color: ${cssManager.bdTheme('#09090b', '#fafafa')};
|
||||||
|
letter-spacing: -0.01em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.grid-actions {
|
.grid-actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 8px;
|
gap: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.grid-actions dees-button {
|
.grid-actions dees-button {
|
||||||
font-size: 14px;
|
font-size: var(--label-font-size);
|
||||||
min-width: auto;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.stats-grid {
|
.stats-grid {
|
||||||
@ -112,86 +128,107 @@ export class DeesStatsGrid extends DeesElement {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Tile Base Styles */
|
||||||
.stats-tile {
|
.stats-tile {
|
||||||
background: ${cssManager.bdTheme('#fff', '#1a1a1a')};
|
background: ${cssManager.bdTheme('#ffffff', '#09090b')};
|
||||||
border: 1px solid ${cssManager.bdTheme('#e0e0e0', '#2a2a2a')};
|
border: 1px solid ${cssManager.bdTheme('hsl(214.3 31.8% 91.4%)', 'hsl(215 20.2% 11.8%)')};
|
||||||
border-radius: 12px;
|
border-radius: var(--border-radius);
|
||||||
padding: 20px;
|
padding: var(--tile-padding);
|
||||||
transition: all 0.3s ease;
|
transition: all var(--transition-duration) ease;
|
||||||
cursor: pointer;
|
cursor: default;
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
.stats-tile:hover {
|
.stats-tile:hover {
|
||||||
transform: translateY(-2px);
|
background: ${cssManager.bdTheme('hsl(210 40% 98%)', 'hsl(215 20.2% 10.2%)')};
|
||||||
box-shadow: 0 4px 12px ${cssManager.bdTheme('rgba(0,0,0,0.1)', 'rgba(0,0,0,0.3)')};
|
border-color: ${cssManager.bdTheme('hsl(214.3 31.8% 85%)', 'hsl(215 20.2% 16.8%)')};
|
||||||
border-color: ${cssManager.bdTheme('#d0d0d0', '#3a3a3a')};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.stats-tile.clickable {
|
.stats-tile.clickable {
|
||||||
cursor: pointer;
|
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 {
|
.tile-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: flex-start;
|
||||||
margin-bottom: 12px;
|
margin-bottom: var(--header-spacing);
|
||||||
width: 100%;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tile-title {
|
.tile-title {
|
||||||
font-size: 14px;
|
font-size: var(--title-font-size);
|
||||||
font-weight: 500;
|
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;
|
margin: 0;
|
||||||
|
letter-spacing: -0.01em;
|
||||||
|
line-height: 1.2;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tile-icon {
|
.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 {
|
.tile-content {
|
||||||
height: 90px;
|
min-height: var(--content-min-height);
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
flex: 1;
|
||||||
position: relative;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.tile-value {
|
.tile-value {
|
||||||
font-size: 32px;
|
font-size: var(--value-font-size);
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: ${cssManager.bdTheme('#333', '#fff')};
|
color: ${cssManager.bdTheme('hsl(215.3 25% 8.8%)', 'hsl(210 40% 98%)')};
|
||||||
line-height: 1.2;
|
line-height: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: baseline;
|
align-items: baseline;
|
||||||
justify-content: center;
|
gap: 4px;
|
||||||
gap: 6px;
|
letter-spacing: -0.025em;
|
||||||
width: 100%;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.tile-unit {
|
.tile-unit {
|
||||||
font-size: 18px;
|
font-size: var(--unit-font-size);
|
||||||
font-weight: 400;
|
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 {
|
.tile-description {
|
||||||
font-size: 12px;
|
font-size: var(--label-font-size);
|
||||||
color: ${cssManager.bdTheme('#888', '#777')};
|
color: ${cssManager.bdTheme('hsl(215.4 16.3% 56.9%)', 'hsl(215 20.2% 55.1%)')};
|
||||||
margin-top: 8px;
|
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 {
|
.gauge-container {
|
||||||
width: 100%;
|
width: 140px;
|
||||||
height: 80px;
|
height: 80px;
|
||||||
position: relative;
|
position: relative;
|
||||||
display: flex;
|
margin-top: -10px;
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.gauge-svg {
|
.gauge-svg {
|
||||||
@ -201,96 +238,130 @@ export class DeesStatsGrid extends DeesElement {
|
|||||||
|
|
||||||
.gauge-background {
|
.gauge-background {
|
||||||
fill: none;
|
fill: none;
|
||||||
stroke: ${cssManager.bdTheme('#e0e0e0', '#2a2a2a')};
|
stroke: ${cssManager.bdTheme('hsl(214.3 31.8% 91.4%)', 'hsl(215 20.2% 21.8%)')};
|
||||||
stroke-width: 6;
|
stroke-width: 8;
|
||||||
}
|
}
|
||||||
|
|
||||||
.gauge-fill {
|
.gauge-fill {
|
||||||
fill: none;
|
fill: none;
|
||||||
stroke-width: 6;
|
stroke-width: 8;
|
||||||
stroke-linecap: round;
|
stroke-linecap: round;
|
||||||
transition: stroke-dashoffset 0.5s ease;
|
transition: stroke-dashoffset 0.6s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.gauge-text {
|
.gauge-text {
|
||||||
fill: ${cssManager.bdTheme('#333', '#fff')};
|
fill: ${cssManager.bdTheme('hsl(215.3 25% 8.8%)', 'hsl(210 40% 98%)')};
|
||||||
font-size: 18px;
|
font-size: var(--value-font-size);
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
text-anchor: middle;
|
text-anchor: middle;
|
||||||
|
dominant-baseline: alphabetic;
|
||||||
|
letter-spacing: -0.025em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.percentage-container {
|
.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 Styles */
|
||||||
|
.percentage-wrapper {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 24px;
|
|
||||||
background: ${cssManager.bdTheme('#f0f0f0', '#2a2a2a')};
|
|
||||||
border-radius: 12px;
|
|
||||||
overflow: hidden;
|
|
||||||
position: relative;
|
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 {
|
.percentage-fill {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background: ${cssManager.bdTheme('#0084ff', '#0066cc')};
|
background: ${cssManager.bdTheme('hsl(215.3 25% 8.8%)', 'hsl(210 40% 98%)')};
|
||||||
transition: width 0.5s ease;
|
transition: width 0.6s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
border-radius: 12px;
|
border-radius: 4px;
|
||||||
}
|
|
||||||
|
|
||||||
.percentage-text {
|
|
||||||
position: absolute;
|
|
||||||
top: 50%;
|
|
||||||
left: 50%;
|
|
||||||
transform: translate(-50%, -50%);
|
|
||||||
font-size: 12px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: ${cssManager.bdTheme('#333', '#fff')};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Trend Styles */
|
||||||
.trend-container {
|
.trend-container {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
|
||||||
position: relative;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: center;
|
gap: 8px;
|
||||||
align-items: center;
|
}
|
||||||
gap: 4px;
|
|
||||||
|
.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 {
|
.trend-svg {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 40px;
|
height: 100%;
|
||||||
flex-shrink: 0;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
.trend-line {
|
.trend-line {
|
||||||
fill: none;
|
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-width: 2;
|
||||||
|
stroke-linejoin: round;
|
||||||
|
stroke-linecap: round;
|
||||||
}
|
}
|
||||||
|
|
||||||
.trend-area {
|
.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 {
|
.text-value {
|
||||||
font-size: 32px;
|
font-size: var(--value-font-size);
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: ${cssManager.bdTheme('#333', '#fff')};
|
color: ${cssManager.bdTheme('hsl(215.3 25% 8.8%)', 'hsl(210 40% 98%)')};
|
||||||
}
|
letter-spacing: -0.025em;
|
||||||
|
|
||||||
.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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Context Menu */
|
||||||
dees-contextmenu {
|
dees-contextmenu {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
z-index: 1000;
|
z-index: 1000;
|
||||||
@ -306,10 +377,14 @@ export class DeesStatsGrid extends DeesElement {
|
|||||||
return html`
|
return html`
|
||||||
${this.gridActions.length > 0 ? html`
|
${this.gridActions.length > 0 ? html`
|
||||||
<div class="grid-header">
|
<div class="grid-header">
|
||||||
<div class="grid-title">Statistics</div>
|
<div class="grid-title"></div>
|
||||||
<div class="grid-actions">
|
<div class="grid-actions">
|
||||||
${this.gridActions.map(action => html`
|
${this.gridActions.map(action => html`
|
||||||
<dees-button @clicked=${() => this.handleGridAction(action)}>
|
<dees-button
|
||||||
|
@clicked=${() => this.handleGridAction(action)}
|
||||||
|
type="outline"
|
||||||
|
size="sm"
|
||||||
|
>
|
||||||
${action.iconName ? html`<dees-icon .iconFA=${action.iconName} size="small"></dees-icon>` : ''}
|
${action.iconName ? html`<dees-icon .iconFA=${action.iconName} size="small"></dees-icon>` : ''}
|
||||||
${action.name}
|
${action.name}
|
||||||
</dees-button>
|
</dees-button>
|
||||||
@ -354,7 +429,7 @@ export class DeesStatsGrid extends DeesElement {
|
|||||||
${this.renderTileContent(tile)}
|
${this.renderTileContent(tile)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
${tile.description ? html`
|
${tile.description && tile.type !== 'trend' ? html`
|
||||||
<div class="tile-description">${tile.description}</div>
|
<div class="tile-description">${tile.description}</div>
|
||||||
` : ''}
|
` : ''}
|
||||||
</div>
|
</div>
|
||||||
@ -396,12 +471,31 @@ export class DeesStatsGrid extends DeesElement {
|
|||||||
const value = typeof tile.value === 'number' ? tile.value : parseFloat(tile.value);
|
const value = typeof tile.value === 'number' ? tile.value : parseFloat(tile.value);
|
||||||
const options = tile.gaugeOptions || { min: 0, max: 100 };
|
const options = tile.gaugeOptions || { min: 0, max: 100 };
|
||||||
const percentage = ((value - options.min) / (options.max - options.min)) * 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;
|
|
||||||
|
|
||||||
let strokeColor = tile.color || cssManager.bdTheme('#0084ff', '#0066cc');
|
// SVG dimensions and calculations
|
||||||
|
const width = 140;
|
||||||
|
const height = 80;
|
||||||
|
const strokeWidth = 8;
|
||||||
|
const padding = strokeWidth / 2 + 2;
|
||||||
|
const radius = 48;
|
||||||
|
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('hsl(215.3 25% 28.8%)', 'hsl(210 40% 78%)');
|
||||||
if (options.thresholds) {
|
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) {
|
if (value >= threshold.value) {
|
||||||
strokeColor = threshold.color;
|
strokeColor = threshold.color;
|
||||||
break;
|
break;
|
||||||
@ -410,30 +504,29 @@ export class DeesStatsGrid extends DeesElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
|
<div class="gauge-wrapper">
|
||||||
<div class="gauge-container">
|
<div class="gauge-container">
|
||||||
<svg class="gauge-svg" viewBox="0 0 80 80">
|
<svg class="gauge-svg" viewBox="0 0 ${width} ${height}" preserveAspectRatio="xMidYMid meet">
|
||||||
<circle
|
<!-- Background arc -->
|
||||||
|
<path
|
||||||
class="gauge-background"
|
class="gauge-background"
|
||||||
cx="40"
|
d="${arcPath}"
|
||||||
cy="40"
|
|
||||||
r="30"
|
|
||||||
transform="rotate(-90 40 40)"
|
|
||||||
/>
|
/>
|
||||||
<circle
|
<!-- Filled arc -->
|
||||||
|
<path
|
||||||
class="gauge-fill"
|
class="gauge-fill"
|
||||||
cx="40"
|
d="${arcPath}"
|
||||||
cy="40"
|
|
||||||
r="30"
|
|
||||||
transform="rotate(-90 40 40)"
|
|
||||||
stroke="${strokeColor}"
|
stroke="${strokeColor}"
|
||||||
stroke-dasharray="${strokeDasharray}"
|
stroke-dasharray="${circumference}"
|
||||||
stroke-dashoffset="${strokeDashoffset}"
|
stroke-dashoffset="${strokeDashoffset}"
|
||||||
/>
|
/>
|
||||||
<text class="gauge-text" x="40" y="40" dy="0.35em">
|
<!-- Value text -->
|
||||||
${value}${tile.unit || ''}
|
<text class="gauge-text" x="${centerX}" y="${centerY}">
|
||||||
|
<tspan>${value}</tspan>${tile.unit ? html`<tspan class="gauge-unit" dx="4">${tile.unit}</tspan>` : ''}
|
||||||
</text>
|
</text>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -442,12 +535,14 @@ export class DeesStatsGrid extends DeesElement {
|
|||||||
const percentage = Math.min(100, Math.max(0, value));
|
const percentage = Math.min(100, Math.max(0, value));
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<div class="percentage-container">
|
<div class="percentage-wrapper">
|
||||||
|
<div class="percentage-value">${percentage}%</div>
|
||||||
|
<div class="percentage-bar">
|
||||||
<div
|
<div
|
||||||
class="percentage-fill"
|
class="percentage-fill"
|
||||||
style="width: ${percentage}%; ${tile.color ? `background: ${tile.color}` : ''}"
|
style="width: ${percentage}%; ${tile.color ? `background: ${tile.color}` : ''}"
|
||||||
></div>
|
></div>
|
||||||
<div class="percentage-text">${percentage}%</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
@ -461,11 +556,14 @@ export class DeesStatsGrid extends DeesElement {
|
|||||||
const max = Math.max(...data);
|
const max = Math.max(...data);
|
||||||
const min = Math.min(...data);
|
const min = Math.min(...data);
|
||||||
const range = max - min || 1;
|
const range = max - min || 1;
|
||||||
const width = 200;
|
const width = 300;
|
||||||
const height = 40;
|
const height = 32;
|
||||||
|
|
||||||
|
// Add padding to prevent clipping
|
||||||
|
const padding = 2;
|
||||||
const points = data.map((value, index) => {
|
const points = data.map((value, index) => {
|
||||||
const x = (index / (data.length - 1)) * width;
|
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}`;
|
return `${x},${y}`;
|
||||||
}).join(' ');
|
}).join(' ');
|
||||||
|
|
||||||
@ -473,13 +571,16 @@ export class DeesStatsGrid extends DeesElement {
|
|||||||
|
|
||||||
return html`
|
return html`
|
||||||
<div class="trend-container">
|
<div class="trend-container">
|
||||||
|
<div class="trend-header">
|
||||||
|
<span class="trend-value">${tile.value}</span>
|
||||||
|
${tile.unit ? html`<span class="trend-unit">${tile.unit}</span>` : ''}
|
||||||
|
${tile.description ? html`<span class="trend-label">${tile.description}</span>` : ''}
|
||||||
|
</div>
|
||||||
|
<div class="trend-graph">
|
||||||
<svg class="trend-svg" viewBox="0 0 ${width} ${height}" preserveAspectRatio="none">
|
<svg class="trend-svg" viewBox="0 0 ${width} ${height}" preserveAspectRatio="none">
|
||||||
<polygon class="trend-area" points="${areaPoints}" />
|
<polygon class="trend-area" points="${areaPoints}" />
|
||||||
<polyline class="trend-line" points="${points}" />
|
<polyline class="trend-line" points="${points}" />
|
||||||
</svg>
|
</svg>
|
||||||
<div class="trend-value">
|
|
||||||
<span>${tile.value}</span>
|
|
||||||
${tile.unit ? html`<span class="tile-unit">${tile.unit}</span>` : ''}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
@ -79,6 +79,11 @@ export class DeesWindowLayer extends DeesElement {
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
z-index: ${this.contentZIndex};
|
z-index: ${this.contentZIndex};
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slotContent > * {
|
||||||
|
pointer-events: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.visible {
|
.visible {
|
||||||
@ -87,9 +92,9 @@ export class DeesWindowLayer extends DeesElement {
|
|||||||
pointer-events: all;
|
pointer-events: all;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<div class="windowOverlay ${this.visible ? 'visible' : null}">
|
<div @click=${this.dispatchClicked} class="windowOverlay ${this.visible ? 'visible' : null}">
|
||||||
</div>
|
</div>
|
||||||
<div @click=${this.dispatchClicked} class="slotContent">
|
<div class="slotContent">
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
@ -46,6 +46,7 @@ export * from './dees-input-multitoggle.js';
|
|||||||
export * from './dees-panel.js';
|
export * from './dees-panel.js';
|
||||||
export * from './dees-pdf.js';
|
export * from './dees-pdf.js';
|
||||||
export * from './dees-searchbar.js';
|
export * from './dees-searchbar.js';
|
||||||
|
export * from './dees-shopping-productcard.js';
|
||||||
export * from './dees-simple-appdash.js';
|
export * from './dees-simple-appdash.js';
|
||||||
export * from './dees-simple-login.js';
|
export * from './dees-simple-login.js';
|
||||||
export * from './dees-speechbubble.js';
|
export * from './dees-speechbubble.js';
|
||||||
|
@ -7,7 +7,7 @@ import {
|
|||||||
css,
|
css,
|
||||||
state,
|
state,
|
||||||
} from '@design.estate/dees-element';
|
} from '@design.estate/dees-element';
|
||||||
import { zIndexLayers } from '../00zindex.js';
|
import { zIndexRegistry } from '../00zindex.js';
|
||||||
|
|
||||||
import { WysiwygFormatting } from './wysiwyg.formatting.js';
|
import { WysiwygFormatting } from './wysiwyg.formatting.js';
|
||||||
|
|
||||||
@ -35,6 +35,9 @@ export class DeesFormattingMenu extends DeesElement {
|
|||||||
@state()
|
@state()
|
||||||
private position: { x: number; y: number } = { x: 0, y: 0 };
|
private position: { x: number; y: number } = { x: 0, y: 0 };
|
||||||
|
|
||||||
|
@state()
|
||||||
|
private menuZIndex: number = 1000;
|
||||||
|
|
||||||
private callback: ((command: string) => void | Promise<void>) | null = null;
|
private callback: ((command: string) => void | Promise<void>) | null = null;
|
||||||
|
|
||||||
public static styles = [
|
public static styles = [
|
||||||
@ -42,12 +45,15 @@ export class DeesFormattingMenu extends DeesElement {
|
|||||||
css`
|
css`
|
||||||
:host {
|
:host {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
z-index: ${zIndexLayers.wysiwygMenus};
|
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.formatting-menu {
|
.formatting-menu {
|
||||||
position: absolute;
|
position: fixed;
|
||||||
background: ${cssManager.bdTheme('#ffffff', '#262626')};
|
background: ${cssManager.bdTheme('#ffffff', '#262626')};
|
||||||
border: 1px solid ${cssManager.bdTheme('#e0e0e0', '#404040')};
|
border: 1px solid ${cssManager.bdTheme('#e0e0e0', '#404040')};
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
@ -119,6 +125,9 @@ export class DeesFormattingMenu extends DeesElement {
|
|||||||
render(): TemplateResult {
|
render(): TemplateResult {
|
||||||
if (!this.visible) return html``;
|
if (!this.visible) return html``;
|
||||||
|
|
||||||
|
// Apply z-index to host element
|
||||||
|
this.style.zIndex = this.menuZIndex.toString();
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<div
|
<div
|
||||||
class="formatting-menu"
|
class="formatting-menu"
|
||||||
@ -153,13 +162,21 @@ export class DeesFormattingMenu extends DeesElement {
|
|||||||
console.log('FormattingMenu.show called:', { position, visible: this.visible });
|
console.log('FormattingMenu.show called:', { position, visible: this.visible });
|
||||||
this.position = position;
|
this.position = position;
|
||||||
this.callback = callback;
|
this.callback = callback;
|
||||||
|
|
||||||
|
// Get z-index from registry and apply immediately
|
||||||
|
this.menuZIndex = zIndexRegistry.getNextZIndex();
|
||||||
|
zIndexRegistry.register(this, this.menuZIndex);
|
||||||
|
this.style.zIndex = this.menuZIndex.toString();
|
||||||
|
|
||||||
this.visible = true;
|
this.visible = true;
|
||||||
console.log('FormattingMenu.show - visible set to:', this.visible);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public hide(): void {
|
public hide(): void {
|
||||||
this.visible = false;
|
this.visible = false;
|
||||||
this.callback = null;
|
this.callback = null;
|
||||||
|
|
||||||
|
// Unregister from z-index registry
|
||||||
|
zIndexRegistry.unregister(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public updatePosition(position: { x: number; y: number }): void {
|
public updatePosition(position: { x: number; y: number }): void {
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import {
|
import {
|
||||||
customElement,
|
customElement,
|
||||||
property,
|
|
||||||
html,
|
html,
|
||||||
DeesElement,
|
DeesElement,
|
||||||
type TemplateResult,
|
type TemplateResult,
|
||||||
@ -8,7 +7,7 @@ import {
|
|||||||
css,
|
css,
|
||||||
state,
|
state,
|
||||||
} from '@design.estate/dees-element';
|
} from '@design.estate/dees-element';
|
||||||
import { zIndexLayers } from '../00zindex.js';
|
import { zIndexRegistry } from '../00zindex.js';
|
||||||
|
|
||||||
import { type ISlashMenuItem } from './wysiwyg.types.js';
|
import { type ISlashMenuItem } from './wysiwyg.types.js';
|
||||||
import { WysiwygShortcuts } from './wysiwyg.shortcuts.js';
|
import { WysiwygShortcuts } from './wysiwyg.shortcuts.js';
|
||||||
@ -43,6 +42,9 @@ export class DeesSlashMenu extends DeesElement {
|
|||||||
@state()
|
@state()
|
||||||
private selectedIndex: number = 0;
|
private selectedIndex: number = 0;
|
||||||
|
|
||||||
|
@state()
|
||||||
|
private menuZIndex: number = 1000;
|
||||||
|
|
||||||
private callback: ((type: string) => void) | null = null;
|
private callback: ((type: string) => void) | null = null;
|
||||||
|
|
||||||
public static styles = [
|
public static styles = [
|
||||||
@ -50,12 +52,15 @@ export class DeesSlashMenu extends DeesElement {
|
|||||||
css`
|
css`
|
||||||
:host {
|
:host {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
z-index: ${zIndexLayers.wysiwygMenus};
|
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.slash-menu {
|
.slash-menu {
|
||||||
position: absolute;
|
position: fixed;
|
||||||
background: ${cssManager.bdTheme('#ffffff', '#262626')};
|
background: ${cssManager.bdTheme('#ffffff', '#262626')};
|
||||||
border: 1px solid ${cssManager.bdTheme('#e0e0e0', '#404040')};
|
border: 1px solid ${cssManager.bdTheme('#e0e0e0', '#404040')};
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
@ -119,6 +124,9 @@ export class DeesSlashMenu extends DeesElement {
|
|||||||
render(): TemplateResult {
|
render(): TemplateResult {
|
||||||
if (!this.visible) return html``;
|
if (!this.visible) return html``;
|
||||||
|
|
||||||
|
// Ensure z-index is applied to host element
|
||||||
|
this.style.zIndex = this.menuZIndex.toString();
|
||||||
|
|
||||||
const menuItems = this.getFilteredMenuItems();
|
const menuItems = this.getFilteredMenuItems();
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
@ -162,6 +170,12 @@ export class DeesSlashMenu extends DeesElement {
|
|||||||
this.callback = callback;
|
this.callback = callback;
|
||||||
this.filter = '';
|
this.filter = '';
|
||||||
this.selectedIndex = 0;
|
this.selectedIndex = 0;
|
||||||
|
|
||||||
|
// Get z-index from registry and apply immediately
|
||||||
|
this.menuZIndex = zIndexRegistry.getNextZIndex();
|
||||||
|
zIndexRegistry.register(this, this.menuZIndex);
|
||||||
|
this.style.zIndex = this.menuZIndex.toString();
|
||||||
|
|
||||||
this.visible = true;
|
this.visible = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -170,6 +184,9 @@ export class DeesSlashMenu extends DeesElement {
|
|||||||
this.callback = null;
|
this.callback = null;
|
||||||
this.filter = '';
|
this.filter = '';
|
||||||
this.selectedIndex = 0;
|
this.selectedIndex = 0;
|
||||||
|
|
||||||
|
// Unregister from z-index registry
|
||||||
|
zIndexRegistry.unregister(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public updateFilter(filter: string): void {
|
public updateFilter(filter: string): void {
|
||||||
|
@ -9,9 +9,10 @@ import '../elements/dees-panel.js';
|
|||||||
import '../elements/dees-input-text.js';
|
import '../elements/dees-input-text.js';
|
||||||
import '../elements/dees-input-radiogroup.js';
|
import '../elements/dees-input-radiogroup.js';
|
||||||
import '../elements/dees-input-tags.js';
|
import '../elements/dees-input-tags.js';
|
||||||
|
import '../elements/dees-input-wysiwyg.js';
|
||||||
import '../elements/dees-appui-profiledropdown.js';
|
import '../elements/dees-appui-profiledropdown.js';
|
||||||
|
|
||||||
export const showcasePage = () => html`
|
export const zIndexShowcase = () => html`
|
||||||
<style>
|
<style>
|
||||||
${css`
|
${css`
|
||||||
.page-wrapper {
|
.page-wrapper {
|
||||||
@ -244,6 +245,16 @@ export const showcasePage = () => html`
|
|||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Consistent panel spacing */
|
||||||
|
dees-panel {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
dees-panel:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.test-area {
|
.test-area {
|
||||||
position: relative;
|
position: relative;
|
||||||
height: 200px;
|
height: 200px;
|
||||||
@ -412,8 +423,8 @@ export const showcasePage = () => html`
|
|||||||
<span class="layer-value">z-index: 2999-3000</span>
|
<span class="layer-value">z-index: 2999-3000</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="layer context">
|
<div class="layer context">
|
||||||
<span class="layer-name">Context Menus & WYSIWYG</span>
|
<span class="layer-name">Context Menus</span>
|
||||||
<span class="layer-value">z-index: 4000-4500</span>
|
<span class="layer-value">z-index: 4000</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="layer toast">
|
<div class="layer toast">
|
||||||
<span class="layer-name">Toast Notifications</span>
|
<span class="layer-name">Toast Notifications</span>
|
||||||
@ -650,6 +661,49 @@ export const showcasePage = () => html`
|
|||||||
}}>Show Multiple Toasts</dees-button>
|
}}>Show Multiple Toasts</dees-button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="demo-card">
|
||||||
|
<h4>Modal with WYSIWYG Editor</h4>
|
||||||
|
<dees-button @click=${async () => {
|
||||||
|
await DeesModal.createAndShow({
|
||||||
|
heading: 'WYSIWYG Editor Test',
|
||||||
|
width: 'large',
|
||||||
|
content: html`
|
||||||
|
<p>Test the WYSIWYG editor slash commands and formatting menus in a modal:</p>
|
||||||
|
<dees-form>
|
||||||
|
<dees-input-wysiwyg
|
||||||
|
.label=${'Document Content'}
|
||||||
|
.placeholder=${'Type "/" to see slash commands or select text to format...'}
|
||||||
|
.outputFormat=${'html'}
|
||||||
|
.description=${'The slash menu and formatting menu should appear above this modal'}
|
||||||
|
.value=${`<p>Welcome to the WYSIWYG editor demo!</p>
|
||||||
|
<p>This editor demonstrates proper z-index management:</p>
|
||||||
|
<ul>
|
||||||
|
<li>Type <strong>/</strong> to open the slash command menu</li>
|
||||||
|
<li>Select any text to see the formatting toolbar</li>
|
||||||
|
<li>Both menus will appear <em>above</em> this modal</li>
|
||||||
|
</ul>
|
||||||
|
<p>Try it now: Type / here or select this text to format it.</p>`}
|
||||||
|
></dees-input-wysiwyg>
|
||||||
|
</dees-form>
|
||||||
|
<div style="margin-top: 16px; padding: 16px; background: ${cssManager.bdTheme('#e3f2fd', '#1e3a5f')}; border-radius: 8px;">
|
||||||
|
<strong style="color: ${cssManager.bdTheme('#1976d2', '#90caf9')}">✨ Z-Index Fix Applied!</strong><br>
|
||||||
|
<span style="color: ${cssManager.bdTheme('#1976d2', '#90caf9')}">
|
||||||
|
The WYSIWYG menus now properly use the dynamic z-index registry.<br>
|
||||||
|
They will always appear above the modal, regardless of stacking order.
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
|
menuOptions: [
|
||||||
|
{ name: 'Cancel', action: async (modal) => modal.destroy() },
|
||||||
|
{ name: 'Save', action: async (modal) => {
|
||||||
|
DeesToast.createAndShow({ message: 'Document saved!', type: 'success' });
|
||||||
|
modal.destroy();
|
||||||
|
}}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
}}>Test WYSIWYG in Modal</dees-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="demo-card">
|
<div class="demo-card">
|
||||||
<h4>Modal with Tags Input</h4>
|
<h4>Modal with Tags Input</h4>
|
||||||
<dees-button @click=${async () => {
|
<dees-button @click=${async () => {
|
||||||
@ -739,6 +793,7 @@ export const showcasePage = () => html`
|
|||||||
<li>Unregister on cleanup: <code>zIndexRegistry.unregister(element)</code></li>
|
<li>Unregister on cleanup: <code>zIndexRegistry.unregister(element)</code></li>
|
||||||
<li>Elements created later automatically appear on top</li>
|
<li>Elements created later automatically appear on top</li>
|
||||||
<li>Test overlay interactions, especially dropdowns in modals</li>
|
<li>Test overlay interactions, especially dropdowns in modals</li>
|
||||||
|
<li>WYSIWYG menus (slash commands, formatting) now use dynamic z-index</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<h4>Import Example:</h4>
|
<h4>Import Example:</h4>
|
||||||
|
Reference in New Issue
Block a user