Compare commits

...

8 Commits

Author SHA1 Message Date
669f12e822 1.10.6
Some checks failed
Default (tags) / security (push) Failing after 52s
Default (tags) / test (push) Failing after 19s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-06-27 17:14:49 +00:00
8b870a8e46 update 2025-06-27 17:14:46 +00:00
9148f0595a update 2025-06-27 17:14:26 +00:00
ea7da1c9b9 update 2025-06-27 16:39:17 +00:00
3e81f54e99 update 2025-06-27 16:29:19 +00:00
65aa9f3c06 update 2025-06-27 16:20:06 +00:00
82ebd9c556 update 2025-06-27 15:58:26 +00:00
50aa071e2e update dees-chart 2025-06-27 15:43:26 +00:00
24 changed files with 2766 additions and 1018 deletions

View File

@ -1,6 +1,6 @@
{
"name": "@design.estate/dees-catalog",
"version": "1.10.5",
"version": "1.10.6",
"private": false,
"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",

View 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;
`);

View File

@ -1,4 +1,3 @@
import { demoFunc } from './dees-button.demo.js';
import {
customElement,
html,
@ -12,6 +11,7 @@ import {
} from '@design.estate/dees-element';
import * as domtools from '@design.estate/dees-domtools';
import { demoFunc } from './dees-button.demo.js';
declare global {
interface HTMLElementTagNameMap {

View File

@ -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 '@design.estate/dees-wcctools/demotools';
@ -402,7 +402,7 @@ export const demoFunc = () => {
${css`
.demoBox {
position: relative;
background: #000000;
background: ${cssManager.bdTheme('hsl(0 0% 95%)', 'hsl(0 0% 9%)')};
height: 100%;
width: 100%;
padding: 40px;
@ -425,9 +425,9 @@ export const demoFunc = () => {
}
.info {
color: #666;
font-size: 11px;
font-family: 'Geist Sans', sans-serif;
color: ${cssManager.bdTheme('hsl(215.4 16.3% 56.9%)', 'hsl(215 20.2% 55.1%)')};
font-size: 12px;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Geist Sans', sans-serif;
text-align: center;
margin-top: 8px;
}

View File

@ -61,6 +61,23 @@ export class DeesChartArea extends DeesElement {
private resizeTimeout: number;
private internalChartData: ApexAxisChartSeries = [];
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() {
super();
@ -73,46 +90,72 @@ export class DeesChartArea extends DeesElement {
}
this.resizeTimeout = window.setTimeout(() => {
for (let entry of entries) {
if (entry.target.classList.contains('mainbox') && this.chart) {
this.resizeChart();
// Simply resize if we have a chart, since we're only observing the mainbox
if (this.chart) {
// 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
});
this.registerStartupFunction(async () => {
this.updateComplete.then(() => {
const mainbox = this.shadowRoot.querySelector('.mainbox');
if (mainbox) {
this.resizeObserver.observe(mainbox);
}
});
});
// Note: ResizeObserver is now set up after chart initialization in firstUpdated()
// to ensure proper timing and avoid race conditions
this.registerGarbageFunction(async () => {
if (this.resizeTimeout) {
clearTimeout(this.resizeTimeout);
}
if (this.resizeObserver) {
this.resizeObserver.disconnect();
}
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 = [
cssManager.defaultStyles,
css`
:host {
font-family: 'Geist Sans', sans-serif;
color: #ccc;
font-weight: 600;
font-size: 12px;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif;
color: ${cssManager.bdTheme('hsl(0 0% 3.9%)', 'hsl(0 0% 98%)')};
font-weight: 400;
font-size: 14px;
}
.mainbox {
position: relative;
width: 100%;
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;
overflow: hidden;
}
@ -122,9 +165,13 @@ export class DeesChartArea extends DeesElement {
top: 0;
left: 0;
width: 100%;
text-align: center;
padding-top: 16px;
text-align: left;
padding: 16px 24px;
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 {
position: absolute;
@ -132,8 +179,22 @@ export class DeesChartArea extends DeesElement {
left: 0px;
bottom: 0px;
right: 0px;
padding: 32px 16px 16px 0px;
padding: 44px 16px 16px 0px;
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
this.internalChartData = chartSeries;
// Get current theme
const isDark = !this.goBright;
const theme = isDark ? 'dark' : 'light';
var options: ApexCharts.ApexOptions = {
series: chartSeries,
chart: {
width: initialWidth || 100, // Use actual width or fallback
height: initialHeight || 100, // Use actual height or fallback
type: 'area',
background: 'transparent', // Transparent background to inherit from container
toolbar: {
show: false, // This line disables the toolbar
},
@ -220,12 +286,18 @@ export class DeesChartArea extends DeesElement {
speed: 350
}
},
zoom: {
enabled: false, // Disable zoom for cleaner interaction
},
selection: {
enabled: false, // Disable selection
},
},
dataLabels: {
enabled: false,
},
stroke: {
width: 1,
width: 2,
curve: 'smooth',
},
xaxis: {
@ -234,8 +306,10 @@ export class DeesChartArea extends DeesElement {
format: 'HH:mm:ss', // Time formatting with seconds
datetimeUTC: false,
style: {
colors: '#9e9e9e', // Label color
fontSize: '11px',
colors: [isDark ? 'hsl(0 0% 63.9%)' : 'hsl(0 0% 20%)'], // Label color
fontSize: '12px',
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif',
fontWeight: '400',
},
},
axisBorder: {
@ -251,8 +325,10 @@ export class DeesChartArea extends DeesElement {
labels: {
formatter: this.yAxisFormatter,
style: {
colors: '#9e9e9e', // Label color
colors: [isDark ? 'hsl(0 0% 63.9%)' : 'hsl(0 0% 20%)'], // Label color
fontSize: '12px',
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif',
fontWeight: '400',
},
},
axisBorder: {
@ -269,14 +345,30 @@ export class DeesChartArea extends DeesElement {
x: {
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
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) => {
const label = w.globals.seriesNames[index]; // Get series label
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>`;
@ -286,7 +378,7 @@ export class DeesChartArea extends DeesElement {
grid: {
xaxis: {
lines: {
show: true, // This enables the grid lines along the x-axis
show: false, // Hide vertical grid lines for cleaner look
},
},
yaxis: {
@ -294,38 +386,67 @@ export class DeesChartArea extends DeesElement {
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
row: {
colors: [], // This can be used to alternate the shading of the horizontal rows
opacity: 0.1,
},
column: {
colors: [], // For vertical column bands, not needed here but available for customization
opacity: 0.1,
padding: {
top: 10,
right: 20,
bottom: 10,
left: 20,
},
},
fill: {
type: 'gradient', // Gradient fill for the area
gradient: {
shade: 'dark',
shade: isDark ? 'dark' : 'light',
type: 'vertical',
gradientToColors: ['#9c27b0'], // Gradient color ending
shadeIntensity: 0.1,
opacityFrom: isDark ? 0.2 : 0.3,
opacityTo: 0,
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);
await this.chart.render();
// Give the chart a moment to fully initialize before resizing
await new Promise(resolve => setTimeout(resolve, 100));
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>) {
super.updated(changedProperties);
// Update chart theme when goBright changes
if (changedProperties.has('goBright') && this.chart) {
await this.updateChartTheme();
}
// Update chart if series data changes
if (changedProperties.has('series') && this.chart && this.series.length > 0) {
await this.updateSeries(this.series);
@ -393,6 +514,7 @@ export class DeesChartArea extends DeesElement {
return;
}
try {
// Store the new data first
this.internalChartData = newSeries;
@ -429,14 +551,18 @@ export class DeesChartArea extends DeesElement {
}
}
this.chart.updateSeries(filteredSeries, false);
await this.chart.updateSeries(filteredSeries, false);
}
} 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() {
if (!this.chart || this.rollingWindow <= 0) {
return;
@ -453,8 +579,10 @@ export class DeesChartArea extends DeesElement {
format: 'HH:mm:ss',
datetimeUTC: false,
style: {
colors: '#9e9e9e',
fontSize: '11px',
colors: [!this.goBright ? 'hsl(0 0% 63.9%)' : 'hsl(0 0% 20%)'],
fontSize: '12px',
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif',
fontWeight: '400',
},
},
tickAmount: 6,
@ -484,6 +612,11 @@ export class DeesChartArea extends DeesElement {
return;
}
if (this.DEBUG_RESIZE) {
console.log('DeesChartArea - resizeChart called');
}
try {
const mainbox: HTMLDivElement = this.shadowRoot.querySelector('.mainbox');
const chartContainer: HTMLDivElement = this.shadowRoot.querySelector('.chartContainer');
@ -491,6 +624,9 @@ export class DeesChartArea extends DeesElement {
return;
}
// Force layout recalculation
void mainbox.offsetHeight;
// Get computed style of the element
const styleChartContainer = window.getComputedStyle(chartContainer);
@ -504,12 +640,33 @@ export class DeesChartArea extends DeesElement {
const actualWidth = mainbox.clientWidth - paddingLeft - paddingRight;
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({
chart: {
width: actualWidth,
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() {
@ -528,4 +685,43 @@ export class DeesChartArea extends DeesElement {
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,
},
},
});
}
}

View File

@ -53,17 +53,17 @@ export class DeesChartLog extends DeesElement {
cssManager.defaultStyles,
css`
:host {
font-family: 'Geist Mono', 'Consolas', 'Monaco', monospace;
color: #ccc;
font-family: 'SF Mono', 'Monaco', 'Consolas', 'Liberation Mono', 'Courier New', monospace;
color: ${cssManager.bdTheme('hsl(0 0% 3.9%)', 'hsl(0 0% 98%)')};
font-size: 12px;
line-height: 1.4;
line-height: 1.5;
}
.mainbox {
position: relative;
width: 100%;
height: 400px;
background: ${cssManager.bdTheme('#f8f9fa', '#0a0a0a')};
border: 1px solid ${cssManager.bdTheme('#dee2e6', '#333')};
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;
display: flex;
flex-direction: column;
@ -71,9 +71,9 @@ export class DeesChartLog extends DeesElement {
}
.header {
background: ${cssManager.bdTheme('#e9ecef', '#1a1a1a')};
padding: 8px 16px;
border-bottom: 1px solid ${cssManager.bdTheme('#dee2e6', '#333')};
background: ${cssManager.bdTheme('hsl(0 0% 97%)', 'hsl(0 0% 7%)')};
padding: 12px 16px;
border-bottom: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')};
display: flex;
justify-content: space-between;
align-items: center;
@ -81,8 +81,10 @@ export class DeesChartLog extends DeesElement {
}
.title {
font-weight: 600;
color: ${cssManager.bdTheme('#212529', '#fff')};
font-weight: 500;
font-size: 14px;
color: ${cssManager.bdTheme('hsl(0 0% 9%)', 'hsl(0 0% 95%)')};
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
}
.controls {
@ -91,44 +93,48 @@ export class DeesChartLog extends DeesElement {
}
.control-button {
background: ${cssManager.bdTheme('#e9ecef', '#2a2a2a')};
border: 1px solid ${cssManager.bdTheme('#ced4da', '#444')};
border-radius: 4px;
padding: 4px 8px;
color: ${cssManager.bdTheme('#495057', '#ccc')};
background: ${cssManager.bdTheme('hsl(0 0% 100%)', 'hsl(0 0% 14.9%)')};
border: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')};
border-radius: 6px;
padding: 6px 12px;
color: ${cssManager.bdTheme('hsl(0 0% 45.1%)', 'hsl(0 0% 63.9%)')};
cursor: pointer;
font-size: 11px;
transition: all 0.2s;
font-size: 12px;
font-weight: 500;
transition: all 0.15s;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
}
.control-button:hover {
background: ${cssManager.bdTheme('#dee2e6', '#3a3a3a')};
border-color: ${cssManager.bdTheme('#adb5bd', '#555')};
background: ${cssManager.bdTheme('hsl(0 0% 95.1%)', 'hsl(0 0% 14.9%)')};
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 {
background: ${cssManager.bdTheme('#007bff', '#4a4a4a')};
color: ${cssManager.bdTheme('#fff', '#fff')};
background: ${cssManager.bdTheme('hsl(0 0% 9%)', 'hsl(0 0% 93.9%)')};
color: ${cssManager.bdTheme('hsl(0 0% 98%)', 'hsl(0 0% 3.9%)')};
}
.logContainer {
flex: 1;
overflow-y: auto;
overflow-x: hidden;
padding: 8px 16px;
padding: 16px;
font-size: 12px;
}
.logEntry {
margin-bottom: 2px;
margin-bottom: 4px;
display: flex;
white-space: pre-wrap;
word-break: break-all;
font-variant-numeric: tabular-nums;
}
.timestamp {
color: ${cssManager.bdTheme('#6c757d', '#666')};
margin-right: 8px;
color: ${cssManager.bdTheme('hsl(0 0% 63.9%)', 'hsl(0 0% 45.1%)')};
margin-right: 12px;
flex-shrink: 0;
}
@ -143,38 +149,38 @@ export class DeesChartLog extends DeesElement {
}
.level.debug {
color: ${cssManager.bdTheme('#6c757d', '#999')};
background: ${cssManager.bdTheme('rgba(108, 117, 125, 0.1)', '#333')};
color: ${cssManager.bdTheme('hsl(0 0% 45.1%)', 'hsl(0 0% 63.9%)')};
background: ${cssManager.bdTheme('hsl(0 0% 45.1% / 0.1)', 'hsl(0 0% 63.9% / 0.1)')};
}
.level.info {
color: ${cssManager.bdTheme('#0066cc', '#4a9eff')};
background: ${cssManager.bdTheme('rgba(0, 102, 204, 0.1)', 'rgba(74, 158, 255, 0.1)')};
color: ${cssManager.bdTheme('hsl(222.2 47.4% 51.2%)', 'hsl(217.2 91.2% 59.8%)')};
background: ${cssManager.bdTheme('hsl(222.2 47.4% 51.2% / 0.1)', 'hsl(217.2 91.2% 59.8% / 0.1)')};
}
.level.warn {
color: ${cssManager.bdTheme('#ff8800', '#ffb84a')};
background: ${cssManager.bdTheme('rgba(255, 136, 0, 0.1)', 'rgba(255, 184, 74, 0.1)')};
color: ${cssManager.bdTheme('hsl(25 95% 53%)', 'hsl(25 95% 63%)')};
background: ${cssManager.bdTheme('hsl(25 95% 53% / 0.1)', 'hsl(25 95% 63% / 0.1)')};
}
.level.error {
color: ${cssManager.bdTheme('#dc3545', '#ff4a4a')};
background: ${cssManager.bdTheme('rgba(220, 53, 69, 0.1)', 'rgba(255, 74, 74, 0.1)')};
color: ${cssManager.bdTheme('hsl(0 84.2% 60.2%)', 'hsl(0 72.2% 50.6%)')};
background: ${cssManager.bdTheme('hsl(0 84.2% 60.2% / 0.1)', 'hsl(0 72.2% 50.6% / 0.1)')};
}
.level.success {
color: ${cssManager.bdTheme('#28a745', '#4aff88')};
background: ${cssManager.bdTheme('rgba(40, 167, 69, 0.1)', 'rgba(74, 255, 136, 0.1)')};
color: ${cssManager.bdTheme('hsl(142.1 76.2% 36.3%)', 'hsl(142.1 70.6% 45.3%)')};
background: ${cssManager.bdTheme('hsl(142.1 76.2% 36.3% / 0.1)', 'hsl(142.1 70.6% 45.3% / 0.1)')};
}
.source {
color: ${cssManager.bdTheme('#6c757d', '#888')};
color: ${cssManager.bdTheme('hsl(0 0% 45.1%)', 'hsl(0 0% 63.9%)')};
margin-right: 8px;
flex-shrink: 0;
}
.message {
color: ${cssManager.bdTheme('#212529', '#ddd')};
color: ${cssManager.bdTheme('hsl(0 0% 15%)', 'hsl(0 0% 90%)')};
flex: 1;
}
@ -183,7 +189,7 @@ export class DeesChartLog extends DeesElement {
align-items: center;
justify-content: center;
height: 100%;
color: ${cssManager.bdTheme('#6c757d', '#666')};
color: ${cssManager.bdTheme('hsl(0 0% 45.1%)', 'hsl(0 0% 63.9%)')};
font-style: italic;
}
@ -193,16 +199,16 @@ export class DeesChartLog extends DeesElement {
}
.logContainer::-webkit-scrollbar-track {
background: ${cssManager.bdTheme('#e9ecef', '#1a1a1a')};
background: ${cssManager.bdTheme('hsl(0 0% 95%)', 'hsl(0 0% 10%)')};
}
.logContainer::-webkit-scrollbar-thumb {
background: ${cssManager.bdTheme('#adb5bd', '#444')};
background: ${cssManager.bdTheme('hsl(0 0% 70%)', 'hsl(0 0% 30%)')};
border-radius: 4px;
}
.logContainer::-webkit-scrollbar-thumb:hover {
background: ${cssManager.bdTheme('#6c757d', '#555')};
background: ${cssManager.bdTheme('hsl(0 0% 60%)', 'hsl(0 0% 40%)')};
}
`,
];

View File

@ -3,47 +3,162 @@ import * as tsclass from '@tsclass/tsclass';
export const demoFunc = () => html` <style>
.demo {
background: ${cssManager.bdTheme('#eeeeeb', '#000000')};
background: ${cssManager.bdTheme('#f5f5f5', '#0a0a0a')};
display: block;
content: '';
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>
<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
.statusObject=${{
id: '1',
name: 'Demo Item',
combinedStatus: 'partly_ok',
combinedStatusText: 'partly_ok',
name: 'API Gateway Service',
combinedStatus: 'ok',
combinedStatusText: 'All systems operational',
details: [
{
name: 'Detail 1',
value: 'Value 1',
name: 'Response Time',
value: '45ms (avg)',
status: 'ok',
statusText: 'OK',
statusText: 'Within normal range',
},
{
name: 'Detail 2',
value: 'Value 2',
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',
name: 'Uptime',
value: '99.99% (30 days)',
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}
>
</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>`;

View File

@ -15,6 +15,7 @@ import {
} from '@design.estate/dees-element';
import * as tsclass from '@tsclass/tsclass';
import { DeesContextmenu } from './dees-contextmenu.js';
declare global {
interface HTMLElementTagNameMap {
@ -31,109 +32,128 @@ export class DeesDataviewStatusobject extends DeesElement {
public static styles = [
cssManager.defaultStyles,
css`
:host {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif;
}
.mainbox {
border-radius: 8px;
background: ${cssManager.bdTheme('#fff', '#1b1b1b')};
box-shadow: 0px 1px 3px #00000030;
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%)')};
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;
color: ${cssManager.bdTheme('#000', '#fff')};
border-top: ${cssManager.bdTheme('none', '1px solid #ffffff10')};
color: ${cssManager.bdTheme('hsl(0 0% 9%)', 'hsl(0 0% 98%)')};
cursor: default;
overflow: hidden;
}
.heading {
display: grid;
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 {
display: block;
margin: 0px;
padding: 0px;
height: 48px;
text-transform: uppercase;
font-size: 12px;
line-height: 48px;
padding: 0px 12px;
font-size: 14px;
font-weight: 500;
letter-spacing: -0.01em;
color: ${cssManager.bdTheme('hsl(0 0% 15%)', 'hsl(0 0% 93.9%)')};
}
.statusdot {
height: 8px;
width: 8px;
border-radius: 6px;
background: grey;
height: 10px;
width: 10px;
border-radius: 50%;
background: ${cssManager.bdTheme('hsl(0 0% 63.9%)', 'hsl(0 0% 45.1%)')};
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 {
font-size: 10px;
font-weight: 600;
text-transform: uppercase;
border: 1px solid ${cssManager.bdTheme('#e0e0e0', '#444')};
font-size: 12px;
font-weight: 500;
background: ${cssManager.bdTheme('hsl(0 0% 100%)', 'hsl(0 0% 14.9%)')};
border: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')};
text-align: center;
padding: 4px;
border-radius: 3px;
margin-right: 16px;
color: ${cssManager.bdTheme('#333', '#ffffff80')};
padding: 6px 12px;
border-radius: 6px;
color: ${cssManager.bdTheme('hsl(0 0% 45.1%)', 'hsl(0 0% 63.9%)')};
user-select: none;
cursor: pointer;
transition: all 0.15s ease;
}
.copyMain:hover {
background: ${cssManager.bdTheme(colors.bright.blue, colors.dark.blue)};
border: 1px solid ${cssManager.bdTheme(colors.bright.blue, colors.dark.blue)};
color: #fff;
background: ${cssManager.bdTheme('hsl(0 0% 95.1%)', 'hsl(0 0% 14.9%)')};
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%)')};
}
.copyMain:active {
background: ${cssManager.bdTheme(colors.bright.blueActive, colors.dark.blueActive)};
border: 1px solid ${cssManager.bdTheme(colors.bright.blueActive, colors.dark.blueActive)};
color: #fff;
background: ${cssManager.bdTheme('hsl(0 0% 91%)', 'hsl(0 0% 14.9%)')};
transform: scale(0.98);
}
.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{
background: red;
.statusdot.not_ok {
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 {
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 {
min-height: 60px;
align-items: center;
display: grid;
grid-template-columns: 40px auto;
border-top: 1px dotted ${cssManager.bdTheme('#e0e0e0', '#282828')};
transition: all 0.2s;
grid-template-columns: 48px auto;
border-top: 1px solid ${cssManager.bdTheme('hsl(0 0% 94%)', 'hsl(0 0% 14.9%)')};
transition: background-color 0.15s ease;
padding-right: 16px;
cursor: context-menu;
}
.detail:hover {
background: #ffffff05;
background: ${cssManager.bdTheme('hsl(0 0% 97%)', 'hsl(0 0% 7%)')};
}
.detail:active {
background: #ffffff10;
background: ${cssManager.bdTheme('hsl(0 0% 95%)', 'hsl(0 0% 9%)')};
}
.detail .detailsText {
padding-top: 8px;
padding-bottom: 8px;
padding-right: 8px;
padding: 12px;
word-break: break-all;
}
.detail .detailsText .label {
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 {
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="heading">
<div class="statusdot ${this.statusObject?.combinedStatus}"></div>
<h1>${this.statusObject?.name || 'no status object assigned'}</h1>
<div class="copyMain">Copy as JSON</div>
<h1>${this.statusObject?.name || 'No status object assigned'}</h1>
<div class="copyMain" @click=${this.handleCopyAsJson}>Copy JSON</div>
</div>
${this.statusObject?.details?.map((detailArg) => {
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="detailsText">
<div class="label">${detailArg.name}</div>
@ -162,4 +210,42 @@ export class DeesDataviewStatusobject extends DeesElement {
}
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);
}
}
}

View File

@ -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 './dees-panel.js';
import type { DeesInputCheckbox } from './dees-input-checkbox.js';
import './dees-button.js';
@ -41,62 +42,49 @@ export const demoFunc = () => html`
margin: 0 auto;
}
.demo-section {
background: #f8f9fa;
border-radius: 8px;
padding: 24px;
dees-panel {
margin-bottom: 24px;
}
@media (prefers-color-scheme: dark) {
.demo-section {
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;
dees-panel:last-child {
margin-bottom: 0;
}
.checkbox-group {
display: flex;
flex-direction: column;
gap: 8px;
gap: 12px;
}
.feature-list {
background: #f0f0f0;
border-radius: 4px;
.horizontal-checkboxes {
display: flex;
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;
margin-bottom: 16px;
margin-top: 16px;
}
@media (prefers-color-scheme: dark) {
.feature-list {
background: #0a0a0a;
.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;
}
.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 {
@ -104,14 +92,26 @@ export const demoFunc = () => html`
gap: 8px;
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>
<div class="demo-container">
<div class="demo-section">
<h3>Basic Checkboxes</h3>
<p>Standard checkbox inputs for boolean selections</p>
<dees-panel .title=${'Basic Checkboxes'} .subtitle=${'Simple checkbox examples with various labels'}>
<div class="checkbox-group">
<dees-input-checkbox
.label=${'I agree to the Terms and Conditions'}
.value=${true}
@ -126,48 +126,78 @@ export const demoFunc = () => html`
<dees-input-checkbox
.label=${'Enable notifications'}
.required=${true}
.value=${false}
.description=${'Receive email updates about your account'}
.key=${'notifications'}
></dees-input-checkbox>
</div>
</dees-panel>
<div class="demo-section">
<h3>Horizontal Layout</h3>
<p>Checkboxes arranged horizontally for compact forms</p>
<dees-panel .title=${'Checkbox States'} .subtitle=${'Different checkbox states and configurations'}>
<div class="checkbox-group">
<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
.label=${'Option A'}
.value=${false}
.layoutMode=${'horizontal'}
.key=${'optionA'}
></dees-input-checkbox>
<dees-input-checkbox
.label=${'Option B'}
.layoutMode=${'horizontal'}
.value=${true}
.layoutMode=${'horizontal'}
.key=${'optionB'}
></dees-input-checkbox>
<dees-input-checkbox
.label=${'Option C'}
.value=${false}
.layoutMode=${'horizontal'}
.key=${'optionC'}
></dees-input-checkbox>
<dees-input-checkbox
.label=${'Option D'}
.layoutMode=${'horizontal'}
.value=${true}
.layoutMode=${'horizontal'}
.key=${'optionD'}
></dees-input-checkbox>
</div>
</div>
<div class="demo-section">
<h3>Feature Selection Example</h3>
<p>Common use case for feature toggles with batch operations</p>
</dees-panel>
<dees-panel .title=${'Feature Selection Example'} .subtitle=${'Common use case for feature toggles with batch operations'}>
<div class="button-group">
<dees-button id="select-all-btn" type="secondary">Select 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>
</div>
</div>
</div>
</dees-panel>
<div class="demo-section">
<h3>States</h3>
<p>Different checkbox states and configurations</p>
<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>
<dees-panel .title=${'Privacy Settings Example'} .subtitle=${'Checkboxes in a typical form context'}>
<div class="form-section">
<h4 class="section-title">Privacy Preferences</h4>
<div class="checkbox-group">
<dees-input-checkbox
.label=${'Remember me on this device'}
.label=${'Share analytics data'}
.value=${true}
.key=${'rememberMe'}
.description=${'Help us improve by sharing anonymous usage data'}
></dees-input-checkbox>
<dees-input-checkbox
.label=${'Make my profile public'}
.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'}
.label=${'Personalized recommendations'}
.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>
</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>
</dees-demowrapper>
`;

View File

@ -44,120 +44,106 @@ export class DeesInputCheckbox extends DeesInputBase<DeesInputCheckbox> {
:host {
position: relative;
cursor: default;
}
:host(:hover) {
filter: ${cssManager.bdTheme('brightness(0.95)', 'brightness(1.1)')};
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif;
}
.maincontainer {
display: flex;
align-items: center;
gap: 12px;
padding: 8px 0px;
color: ${cssManager.bdTheme('#333', '#ccc')};
display: inline-flex;
align-items: flex-start;
gap: 8px;
cursor: pointer;
user-select: none;
transition: all 0.2s;
}
.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;
transition: all 0.15s ease;
}
.checkbox {
transition: all 0.1s;
box-sizing: border-box;
border: 1px solid ${cssManager.bdTheme('#CCC', '#999')};
border-radius: 2px;
height: 24px;
width: 24px;
display: inline-block;
background: ${cssManager.bdTheme('#fafafa', '#222')};
position: relative;
height: 18px;
width: 18px;
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 {
background: #0050b9;
border: 1px solid #0050b9;
background: ${cssManager.bdTheme('hsl(222.2 47.4% 51.2%)', 'hsl(217.2 91.2% 59.8%)')};
border-color: ${cssManager.bdTheme('hsl(222.2 47.4% 51.2%)', 'hsl(217.2 91.2% 59.8%)')};
}
.checkbox.disabled {
background: none;
border: 1px dashed ${cssManager.bdTheme('#666666', '#666666')};
.checkbox:focus-visible {
outline: none;
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 {
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;
width: 3px;
height: 9px;
background-color: #fff;
left: 11px;
top: 6px;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
opacity: 0;
transition: opacity 0.15s ease;
}
.checkbox .checkmark_kick {
position: absolute;
width: 3px;
height: 3px;
background-color: #fff;
left: 8px;
top: 12px;
.checkbox.selected .checkmark {
opacity: 1;
}
.checkbox.disabled .checkmark_stem, .checkbox.disabled .checkmark_kick {
background-color: ${cssManager.bdTheme('#333', '#fff')};
}
img {
padding: 4px;
}
.checkbox-label {
font-size: 14px;
transition: color 0.2s ease;
}
.maincontainer:hover .checkbox-label {
color: ${cssManager.bdTheme('#1a1a1a', '#ffffff')};
.checkbox .checkmark svg {
width: 12px;
height: 12px;
stroke: white;
stroke-width: 3;
}
/* Disabled state */
.maincontainer.disabled {
cursor: not-allowed;
opacity: 0.5;
}
.maincontainer.disabled:hover {
color: ${cssManager.bdTheme('#333', '#ccc')};
.checkbox.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%)')};
}
.maincontainer.disabled:hover .checkbox {
border-color: ${cssManager.bdTheme('#ccc', '#333')};
/* Label */
.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 {
font-size: 12px;
color: ${cssManager.bdTheme('#666', '#999')};
margin-top: 4px;
line-height: 1.4;
padding-left: 36px;
color: ${cssManager.bdTheme('hsl(0 0% 45.1%)', 'hsl(0 0% 63.9%)')};
line-height: 1.5;
}
`,
];
@ -166,21 +152,26 @@ export class DeesInputCheckbox extends DeesInputBase<DeesInputCheckbox> {
return html`
<div class="input-wrapper">
<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
? html`
<span class="checkmark">
<div class="checkmark_stem"></div>
<div class="checkmark_kick"></div>
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M20 6L9 17L4 12" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</span>
`
: html``}
</div>
<div class="label-container">
${this.label ? html`<div class="checkbox-label">${this.label}</div>` : ''}
${this.description ? html`<div class="description-text">${this.description}</div>` : ''}
</div>
</div>
${this.description ? html`
<div class="description-text">${this.description}</div>
` : ''}
</div>
`;
}
@ -213,4 +204,11 @@ export class DeesInputCheckbox extends DeesInputBase<DeesInputCheckbox> {
(checkboxDiv as any).focus();
}
}
private handleKeydown(event: KeyboardEvent) {
if (event.key === ' ' || event.key === 'Enter') {
event.preventDefault();
this.toggleSelected();
}
}
}

View File

@ -9,7 +9,6 @@ import {
} from '@design.estate/dees-element';
import * as domtools from '@design.estate/dees-domtools';
import { demoFunc } from './dees-input-dropdown.demo.js';
import { DeesWindowLayer } from './dees-windowlayer.js';
import { DeesInputBase } from './dees-input-base.js';
declare global {
@ -39,13 +38,11 @@ export class DeesInputDropdown extends DeesInputBase<DeesInputDropdown> {
this.selectedOption = val;
}
@property({
type: Boolean,
})
public enableSearch: boolean = true;
@state()
public opensToTop: boolean = false;
@ -58,6 +55,9 @@ export class DeesInputDropdown extends DeesInputBase<DeesInputDropdown> {
@state()
public isOpened = false;
@state()
private searchValue: string = '';
public static styles = [
...DeesInputBase.baseStyles,
cssManager.defaultStyles,
@ -67,123 +67,201 @@ export class DeesInputDropdown extends DeesInputBase<DeesInputDropdown> {
}
:host {
font-family: Roboto;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif;
position: relative;
color: ${cssManager.bdTheme('#222', '#fff')};
color: ${cssManager.bdTheme('hsl(0 0% 15%)', 'hsl(0 0% 90%)')};
}
.maincontainer {
display: block;
position: relative;
}
.selectedBox {
user-select: none;
position: relative;
max-width: 420px;
width: 100%;
height: 40px;
line-height: 40px;
padding: 0px 8px;
background: ${cssManager.bdTheme('#fafafa', '#222222')};
box-shadow: ${cssManager.bdTheme('0px 1px 4px rgba(0,0,0,0.3)', 'none')};
border-radius: 3px;
border-top: ${cssManager.bdTheme('1px solid #CCC', '1px solid #ffffff10')};
border-bottom: ${cssManager.bdTheme('1px solid #CCC', '1px solid #222')};
transition: all 0.2s ease;
font-size: 16px;
color: ${cssManager.bdTheme('#222', '#ccc')};
line-height: 38px;
padding: 0 40px 0 12px;
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;
font-size: 14px;
color: ${cssManager.bdTheme('hsl(0 0% 9%)', 'hsl(0 0% 95%)')};
cursor: pointer;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.selectedBox:hover {
filter: ${cssManager.bdTheme('brightness(0.95)', 'brightness(1.1)')};
.selectedBox:hover:not(.disabled) {
border-color: ${cssManager.bdTheme('hsl(0 0% 79.8%)', 'hsl(0 0% 20.9%)')};
}
.accentBottom {
filter: none !important;
.selectedBox:focus-visible {
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 {
filter: none !important;
.selectedBox.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%)')};
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 {
will-change: transform;
will-change: transform, opacity;
pointer-events: none;
transition: all 0.2s ease;
transition: all 0.15s ease;
opacity: 0;
background: ${cssManager.bdTheme('#ffffff', '#222222')};
max-width: 420px;
box-shadow: 0px 0px 5px rgba(0, 0, 0, 0.2);
transform: translateY(-8px) scale(0.98);
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%)')};
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;
border-radius: 8px;
padding: 4px 8px;
max-height: 300px;
overflow: hidden;
border-radius: 6px;
position: absolute;
user-select: none;
margin: 3px 0px 0px 0px;
border-top: ${cssManager.bdTheme('1px solid #CCC', '1px solid #ffffff10')};
margin-top: 4px;
z-index: 50;
left: 0;
right: 0;
}
.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 {
transform: translate(0px, -4px);
top: 100%;
}
.selectionBox.show {
pointer-events: all;
transform: scale(1, 1) translate(0px, 0px);
transform: translateY(0) scale(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 {
transition: all 0.1s;
transition: all 0.15s ease;
line-height: 32px;
padding: 0px 4px;
border-radius: 3px;
margin: 4px 0px;
padding: 0 8px;
border-radius: 4px;
margin: 2px 0;
cursor: pointer;
font-size: 14px;
color: ${cssManager.bdTheme('hsl(0 0% 15%)', 'hsl(0 0% 90%)')};
}
.option.highlighted {
border-left: 2px solid #0069f2;
padding-left: 6px;
background: #ffffff20;
background: ${cssManager.bdTheme('hsl(0 0% 95.1%)', 'hsl(0 0% 14.9%)')};
}
.option:hover {
color: #fff;
padding-left: 8px;
background: #0069f2;
background: ${cssManager.bdTheme('hsl(0 0% 95.1%)', 'hsl(0 0% 14.9%)')};
color: ${cssManager.bdTheme('hsl(0 0% 9%)', 'hsl(0 0% 95%)')};
}
.search.top {
padding-top: 4px;
/* No options message */
.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 {
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 {
display: block;
background: none;
border: none;
height: 24px;
color: inherit;
text-align: left;
font-size: 12px;
font-weight: 600;
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 {
border-bottom: 1px dotted #333;
}
.search.bottom input {
border-top: 1px dotted #333;
.search input::placeholder {
color: ${cssManager.bdTheme('hsl(0 0% 63.9%)', 'hsl(0 0% 45.1%)')};
}
.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 {
return html`
<div class="input-wrapper">
<dees-label .label=${this.label}></dees-label>
<div class="maincontainer" @keydown="${this.isOpened ? this.handleKeyDown : undefined}">
<div class="selectionBox">
${this.enableSearch && !this.opensToTop
<dees-label .label=${this.label} .description=${this.description} .required=${this.required}></dees-label>
<div class="maincontainer">
<div
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`
<div class="search top">
<input type="text" placeholder="Search" @input="${this.handleSearch}" />
<div class="search">
<input
type="text"
placeholder="Search options..."
.value="${this.searchValue}"
@input="${this.handleSearch}"
@click="${(e: Event) => e.stopPropagation()}"
@keydown="${this.handleSearchKeydown}"
/>
</div>
`
: 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;
return html`
<div
class="option ${isHighlighted ? 'highlighted' : ''}"
@click=${() => {
this.updateSelection(option);
}}
@click="${() => this.updateSelection(option)}"
@mouseenter="${() => this.highlightedIndex = index}"
>
${option.option}
</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);
})
}
}}"
>
${this.selectedOption?.option || 'Select...'}
</div>
</div>
</div>
</div>
`;
}
firstUpdated() {
this.selectedOption = this.selectedOption || null;
this.filteredOptions = this.options; // Initialize filteredOptions
async connectedCallback() {
super.connectedCallback();
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.isOpened = false;
this.searchValue = '';
this.filteredOptions = this.options;
this.highlightedIndex = 0;
this.dispatchEvent(
new CustomEvent('selectedOption', {
@ -253,135 +348,105 @@ export class DeesInputDropdown extends DeesInputBase<DeesInputDropdown> {
bubbles: true,
})
);
if (this.isElevated) {
this.toggleSelectionBox();
}
this.changeSubject.next(this);
}
private isElevated: boolean = false;
private windowOverlay: DeesWindowLayer;
private handleClickOutside = (event: MouseEvent) => {
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() {
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
const dropdownZIndex = (await import('./00zindex.js')).zIndexRegistry.getNextZIndex();
elevatedDropdown.style.zIndex = dropdownZIndex.toString();
(await import('./00zindex.js')).zIndexRegistry.register(elevatedDropdown, dropdownZIndex);
elevatedDropdown.options = this.options;
elevatedDropdown.selectedOption = this.selectedOption;
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);
if (this.isOpened) {
// Check available space and set position
const selectedBox = this.shadowRoot.querySelector('.selectedBox') as HTMLElement;
const rect = selectedBox.getBoundingClientRect();
const spaceBelow = window.innerHeight - rect.bottom;
const spaceAbove = rect.top;
// Prevent clicks on the dropdown from closing it
elevatedDropdown.addEventListener('click', (e: Event) => {
e.stopPropagation();
});
// Determine if we should open upwards
this.opensToTop = spaceBelow < 300 && spaceAbove > spaceBelow;
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
(await import('./00zindex.js')).zIndexRegistry.unregister(elevatedDropdown);
this.windowOverlay.destroy();
};
const handleSelection = async () => {
await this.updateSelection(elevatedDropdown.selectedOption);
destroyOverlay();
};
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';
// Focus search input if present
await this.updateComplete;
const searchInput = this.shadowRoot.querySelector('.search input') as HTMLInputElement;
if (searchInput) {
searchInput.focus();
}
await domtoolsInstance.convenience.smartdelay.delayFor(0);
const searchInput = selectionBox.querySelector('input');
searchInput?.focus();
// Get z-index from registry for the selection box
const selectionBoxZIndex = (await import('./00zindex.js')).zIndexRegistry.getNextZIndex();
selectionBox.style.zIndex = selectionBoxZIndex.toString();
(await import('./00zindex.js')).zIndexRegistry.register(selectionBox as HTMLElement, selectionBoxZIndex);
selectionBox.classList.add('show');
// Add click outside listener
setTimeout(() => {
document.addEventListener('click', this.handleClickOutside);
}, 0);
} else {
selectedBox.style.pointerEvents = 'none';
selectionBox.classList.remove('show');
// Unregister selection box from z-index registry
(await import('./00zindex.js')).zIndexRegistry.unregister(selectionBox as HTMLElement);
// selectedBox.style.opacity = '0';
}
// Cleanup
this.searchValue = '';
this.filteredOptions = this.options;
document.removeEventListener('click', this.handleClickOutside);
}
}
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) =>
option.option.toLowerCase().includes(searchTerm)
option.option.toLowerCase().includes(searchLower)
);
this.highlightedIndex = 0; // Reset highlighted index
this.highlightedIndex = 0;
}
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 maxIndex = this.filteredOptions.length - 1;
if (key === 'ArrowDown') {
event.preventDefault();
this.highlightedIndex = this.highlightedIndex + 1 > maxIndex ? 0 : this.highlightedIndex + 1;
event.preventDefault();
} else if (key === 'ArrowUp') {
event.preventDefault();
this.highlightedIndex = this.highlightedIndex - 1 < 0 ? maxIndex : this.highlightedIndex - 1;
event.preventDefault();
} else if (key === 'Enter') {
this.updateSelection(this.filteredOptions[this.highlightedIndex]);
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;
}
}
}
@ -392,4 +457,9 @@ export class DeesInputDropdown extends DeesInputBase<DeesInputDropdown> {
public setValue(value: { option: string; key: string; payload?: any }): void {
this.selectedOption = value;
}
async disconnectedCallback() {
await super.disconnectedCallback();
document.removeEventListener('click', this.handleClickOutside);
}
}

View File

@ -68,7 +68,7 @@ export class DeesInputFileupload extends DeesInputBase<DeesInputFileupload> {
:host {
position: relative;
display: block;
color: ${cssManager.bdTheme('#333', '#ccc')};
color: ${cssManager.bdTheme('hsl(0 0% 15%)', 'hsl(0 0% 90%)')};
}
.hidden {
@ -83,16 +83,16 @@ export class DeesInputFileupload extends DeesInputBase<DeesInputFileupload> {
.maincontainer {
position: relative;
border-radius: 8px;
border-radius: 6px;
padding: 16px;
background: ${cssManager.bdTheme('#f8f9fa', '#1a1a1a')};
color: ${cssManager.bdTheme('#333', '#ccc')};
border: 1px solid ${cssManager.bdTheme('#e0e0e0', '#333')};
transition: all 0.2s ease;
background: ${cssManager.bdTheme('hsl(210 40% 98%)', 'hsl(215 20.2% 11.8%)')};
color: ${cssManager.bdTheme('hsl(0 0% 9%)', 'hsl(0 0% 95%)')};
border: 1px solid ${cssManager.bdTheme('hsl(215 20.2% 65.1%)', 'hsl(215 20.2% 35.1%)')};
transition: all 0.15s ease;
}
.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 {
@ -102,69 +102,69 @@ export class DeesInputFileupload extends DeesInputBase<DeesInputFileupload> {
}
: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 {
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 {
border-color: #f39c12;
border-color: ${cssManager.bdTheme('hsl(45.4 93.4% 47.5%)', 'hsl(45.4 93.4% 47.5%)')};
}
.maincontainer::after {
top: 2px;
right: 2px;
left: 2px;
bottom: 2px;
transform: scale3d(0.98, 0.9, 1);
top: 1px;
right: 1px;
left: 1px;
bottom: 1px;
transform: scale3d(0.98, 0.95, 1);
position: absolute;
content: '';
display: block;
border: 2px dashed transparent;
border-radius: 6px;
transition: all 0.3s ease;
border-radius: 5px;
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
pointer-events: none;
background: transparent;
}
.maincontainer.dragOver {
border-color: ${cssManager.bdTheme('#0084ff', '#0084ff')};
background: ${cssManager.bdTheme('#f0f8ff', '#001933')};
border-color: ${cssManager.bdTheme('hsl(217.2 91.2% 59.8%)', 'hsl(213.1 93.9% 67.8%)')};
background: ${cssManager.bdTheme('hsl(217.2 91.2% 59.8% / 0.05)', 'hsl(213.1 93.9% 67.8% / 0.05)')};
}
.maincontainer.dragOver::after {
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 {
position: relative;
padding: 12px 24px;
background: ${cssManager.bdTheme('#0084ff', '#0084ff')};
color: white;
padding: 10px 20px;
background: ${cssManager.bdTheme('hsl(0 0% 100%)', 'hsl(0 0% 7.8%)')};
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;
text-align: center;
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s ease;
border: none;
transition: all 0.15s ease;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
line-height: 20px;
}
.uploadButton:hover {
background: ${cssManager.bdTheme('#0073e6', '#0073e6')};
transform: translateY(-1px);
box-shadow: 0 2px 8px rgba(0, 132, 255, 0.3);
background: ${cssManager.bdTheme('hsl(0 0% 95.1%)', 'hsl(0 0% 14.9%)')};
border-color: ${cssManager.bdTheme('hsl(0 0% 79.8%)', 'hsl(0 0% 20.9%)')};
}
.uploadButton:active {
transform: translateY(0);
background: ${cssManager.bdTheme('hsl(0 0% 91%)', 'hsl(0 0% 11%)')};
}
.uploadButton dees-icon {
@ -181,21 +181,21 @@ export class DeesInputFileupload extends DeesInputBase<DeesInputFileupload> {
.uploadCandidate {
display: grid;
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;
text-align: left;
border-radius: 6px;
color: ${cssManager.bdTheme('#333', '#ccc')};
color: ${cssManager.bdTheme('hsl(0 0% 9%)', 'hsl(0 0% 95%)')};
cursor: default;
transition: all 0.2s;
border: 1px solid ${cssManager.bdTheme('#e0e0e0', '#333')};
transition: all 0.15s ease;
border: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')};
position: relative;
overflow: hidden;
}
.uploadCandidate:hover {
background: ${cssManager.bdTheme('#f5f5f5', '#333')};
border-color: ${cssManager.bdTheme('#ccc', '#444')};
background: ${cssManager.bdTheme('hsl(0 0% 95.1%)', 'hsl(215 20.2% 20.8%)')};
border-color: ${cssManager.bdTheme('hsl(0 0% 79.8%)', 'hsl(0 0% 20.9%)')};
}
.uploadCandidate .icon {
@ -203,19 +203,19 @@ export class DeesInputFileupload extends DeesInputBase<DeesInputFileupload> {
align-items: center;
justify-content: center;
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 {
color: #4CAF50;
color: ${cssManager.bdTheme('hsl(142.1 70.6% 45.3%)', 'hsl(142.1 76.2% 36.3%)')};
}
.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 {
color: #2196F3;
color: ${cssManager.bdTheme('hsl(217.2 91.2% 59.8%)', 'hsl(213.1 93.9% 67.8%)')};
}
.uploadCandidate .info {
@ -235,7 +235,7 @@ export class DeesInputFileupload extends DeesInputBase<DeesInputFileupload> {
.uploadCandidate .filesize {
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 {
@ -254,13 +254,13 @@ export class DeesInputFileupload extends DeesInputBase<DeesInputFileupload> {
display: flex;
align-items: center;
justify-content: center;
transition: all 0.2s;
color: ${cssManager.bdTheme('#666', '#999')};
transition: all 0.15s ease;
color: ${cssManager.bdTheme('hsl(215.4 16.3% 56.9%)', 'hsl(215 20.2% 55.1%)')};
}
.remove-button:hover {
background: ${cssManager.bdTheme('#fee', '#4a1c1c')};
color: ${cssManager.bdTheme('#e74c3c', '#ff6b6b')};
background: ${cssManager.bdTheme('hsl(0 72.2% 50.6% / 0.1)', 'hsl(0 62.8% 30.6% / 0.1)')};
color: ${cssManager.bdTheme('hsl(0 72.2% 50.6%)', 'hsl(0 62.8% 30.6%)')};
}
.clear-all-button {
@ -271,36 +271,37 @@ export class DeesInputFileupload extends DeesInputBase<DeesInputFileupload> {
.clear-all-button button {
background: 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;
font-size: 12px;
padding: 4px 8px;
border-radius: 4px;
transition: all 0.2s;
transition: all 0.15s ease;
}
.clear-all-button button:hover {
background: ${cssManager.bdTheme('#fee', '#4a1c1c')};
color: ${cssManager.bdTheme('#e74c3c', '#ff6b6b')};
background: ${cssManager.bdTheme('hsl(0 72.2% 50.6% / 0.1)', 'hsl(0 62.8% 30.6% / 0.1)')};
color: ${cssManager.bdTheme('hsl(0 72.2% 50.6%)', 'hsl(0 62.8% 30.6%)')};
}
.validation-message {
font-size: 12px;
margin-top: 4px;
color: #e74c3c;
font-size: 13px;
margin-top: 6px;
color: ${cssManager.bdTheme('hsl(0 72.2% 50.6%)', 'hsl(0 62.8% 30.6%)')};
line-height: 1.5;
}
.drop-hint {
text-align: center;
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;
}
.drop-hint dees-icon {
font-size: 48px;
margin-bottom: 16px;
opacity: 0.3;
opacity: 0.2;
}
.image-preview {
@ -311,10 +312,10 @@ export class DeesInputFileupload extends DeesInputBase<DeesInputFileupload> {
}
.description-text {
font-size: 12px;
color: ${cssManager.bdTheme('#666', '#999')};
margin-top: 4px;
line-height: 1.4;
font-size: 13px;
color: ${cssManager.bdTheme('hsl(215.4 16.3% 56.9%)', 'hsl(215 20.2% 55.1%)')};
margin-top: 6px;
line-height: 1.5;
}
`,
];

View File

@ -1,4 +1,5 @@
import { html, css, cssManager } from '@design.estate/dees-element';
import './dees-shopping-productcard.js';
export const demoFunc = () => html`
<dees-demowrapper>
@ -15,25 +16,44 @@ export const demoFunc = () => html`
.shopping-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
gap: 16px;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 20px;
}
.product-card {
padding: 16px;
background: ${cssManager.bdTheme('#fff', '#2a2a2a')};
border-radius: 4px;
box-shadow: 0 2px 4px ${cssManager.bdTheme('rgba(0,0,0,0.1)', 'rgba(0,0,0,0.3)')};
.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;
}
.product-name {
.cart-summary-title {
font-size: 18px;
font-weight: 600;
margin-bottom: 8px;
margin-bottom: 16px;
color: ${cssManager.bdTheme('hsl(0 0% 9%)', 'hsl(0 0% 95%)')};
}
.product-price {
color: #1976d2;
margin-bottom: 16px;
.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%)')};
}
`}
</style>
@ -53,36 +73,97 @@ export const demoFunc = () => html`
></dees-input-quantityselector>
</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="product-card">
<div class="product-name">Premium Headphones</div>
<div class="product-price">$199.99</div>
<dees-input-quantityselector
.label=${'Quantity'}
.layoutMode=${'horizontal'}
.value=${1}
></dees-input-quantityselector>
<dees-shopping-productcard
id="headphones-qty"
.productData=${{
name: 'Sony WH-1000XM5 Wireless Headphones',
category: 'Audio',
description: 'Industry-leading noise canceling with Auto NC Optimizer',
price: 349.99,
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 class="product-card">
<div class="product-name">Wireless Mouse</div>
<div class="product-price">$49.99</div>
<dees-input-quantityselector
.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 class="cart-summary">
<h3 class="cart-summary-title">Order Summary</h3>
<div id="cart-summary-content">
<!-- Content will be dynamically updated -->
</div>
</div>
</dees-panel>

View File

@ -32,48 +32,91 @@ export class DeesInputQuantitySelector extends DeesInputBase<DeesInputQuantitySe
}
.quantity-container {
transition: all 0.1s;
transition: all 0.15s ease;
font-size: 14px;
display: grid;
grid-template-columns: 33% 34% 33%;
text-align: center;
background: ${cssManager.bdTheme('#fafafa', '#222222')};
line-height: 40px;
padding: 0px;
min-width: 110px;
color: ${cssManager.bdTheme('#666', '#CCC')};
border: 1px solid ${cssManager.bdTheme('#CCC', '#444')};
border-radius: 4px;
display: inline-flex;
align-items: center;
background: transparent;
height: 40px;
padding: 0;
min-width: 120px;
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;
overflow: hidden;
}
.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;
pointer-events: none;
}
.quantity-container:hover {
color: ${cssManager.bdTheme('#333', '#fff')};
border-color: ${cssManager.bdTheme('#999', '#666')};
.quantity-container:hover:not(.disabled) {
border-color: ${cssManager.bdTheme('hsl(0 0% 79.8%)', 'hsl(0 0% 20.9%)')};
}
.minus {
padding-left: 5px;
}
.plus {
padding-right: 5px;
.quantity-container:focus-within {
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)')};
}
.selector {
text-align: center;
font-size: 20px;
flex: 0 0 40px;
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 {
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 {
flex: 1;
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 {
return html`
<div class="input-wrapper">
<dees-label .label=${this.label}></dees-label>
<div class="quantity-container ${this.disabled ? 'disabled' : ''}">
<div class="selector minus" @click="${() => {this.decrease();}}">-</div>
<div class="quantity">${this.value}</div>
<div class="selector plus" @click="${() => {this.increase();}}">+</div>
${this.label ? html`<dees-label .label=${this.label} .description=${this.description} .required=${this.required}></dees-label>` : ''}
<div
class="quantity-container ${this.disabled ? 'disabled' : ''}"
data-min="${this.value <= 0}"
>
<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>
`;

View File

@ -69,80 +69,97 @@ export class DeesInputRadiogroup extends DeesInputBase<string | object> {
:host {
display: block;
position: relative;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif;
}
.maincontainer {
display: flex;
flex-direction: column;
gap: 8px;
gap: 10px;
}
.maincontainer.horizontal {
flex-direction: row;
flex-wrap: wrap;
gap: 16px;
gap: 20px;
}
.radio-option {
display: flex;
align-items: center;
gap: 12px;
padding: 8px 0;
gap: 10px;
padding: 6px 0;
cursor: pointer;
transition: all 0.2s ease;
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
user-select: none;
position: relative;
border-radius: 4px;
}
.maincontainer.horizontal .radio-option {
padding: 8px 16px 8px 0;
padding: 6px 20px 6px 0;
}
.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 {
color: ${cssManager.bdTheme('#1a1a1a', '#ffffff')};
color: ${cssManager.bdTheme('hsl(215.3 25% 8.8%)', 'hsl(210 40% 98%)')};
}
.radio-circle {
width: 20px;
height: 20px;
border-radius: 50%;
border: 2px solid ${cssManager.bdTheme('#999', '#666')};
background: ${cssManager.bdTheme('#fff', '#1a1a1a')};
transition: all 0.2s ease;
border: 2px solid ${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%)')};
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
position: relative;
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: center;
}
.radio-option.selected .radio-circle {
border-color: ${cssManager.bdTheme('#0050b9', '#0084ff')};
background: ${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('hsl(217.2 91.2% 59.8%)', 'hsl(213.1 93.9% 67.8%)')};
}
.radio-option.selected .radio-circle::after {
content: '';
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 8px;
height: 8px;
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 {
font-size: 14px;
color: ${cssManager.bdTheme('#666', '#999')};
transition: color 0.2s ease;
font-weight: 500;
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 {
color: ${cssManager.bdTheme('#1a1a1a', '#ffffff')};
font-weight: 500;
color: ${cssManager.bdTheme('hsl(215.3 25% 8.8%)', 'hsl(210 40% 98%)')};
}
:host([disabled]) .radio-option {
@ -151,40 +168,49 @@ export class DeesInputRadiogroup extends DeesInputBase<string | object> {
}
: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 {
color: ${cssManager.bdTheme('#666', '#999')};
color: ${cssManager.bdTheme('hsl(215.3 25% 26.7%)', 'hsl(217.9 10.6% 74.9%)')};
}
.label-text {
font-size: 14px;
font-weight: 500;
color: ${cssManager.bdTheme('#333', '#ccc')};
margin-bottom: 8px;
color: ${cssManager.bdTheme('hsl(215.3 25% 8.8%)', 'hsl(210 40% 98%)')};
margin-bottom: 10px;
letter-spacing: -0.006em;
line-height: 20px;
}
.description-text {
font-size: 12px;
color: ${cssManager.bdTheme('#666', '#999')};
margin-top: 8px;
line-height: 1.4;
font-size: 13px;
color: ${cssManager.bdTheme('hsl(215.4 16.3% 56.9%)', 'hsl(215 20.2% 55.1%)')};
margin-top: 10px;
line-height: 1.5;
letter-spacing: -0.003em;
}
/* Validation styles */
: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 {
border-color: #27ae60;
background: #27ae60;
border-color: ${cssManager.bdTheme('hsl(142.1 70.6% 45.3%)', 'hsl(142.1 76.2% 36.3%)')};
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 {
border-color: #f39c12;
background: #f39c12;
border-color: ${cssManager.bdTheme('hsl(45.4 93.4% 47.5%)', 'hsl(45.4 93.4% 47.5%)')};
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 */
@ -212,8 +238,15 @@ export class DeesInputRadiogroup extends DeesInputBase<string | object> {
<div
class="radio-option ${isSelected ? 'selected' : ''}"
@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>
`;
@ -292,4 +325,33 @@ export class DeesInputRadiogroup extends DeesInputBase<string | object> {
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();
}
}

View File

@ -53,7 +53,7 @@ export class DeesInputTags extends DeesInputBase<DeesInputTags> {
css`
:host {
display: block;
font-family: 'Geist Sans', sans-serif;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif;
}
.input-wrapper {
@ -64,44 +64,51 @@ export class DeesInputTags extends DeesInputBase<DeesInputTags> {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 8px;
padding: 8px;
gap: 6px;
padding: 6px 10px;
min-height: 40px;
background: ${cssManager.bdTheme('#fafafa', '#222222')};
border: 1px solid ${cssManager.bdTheme('#e0e0e0', '#333333')};
border-radius: 8px;
transition: all 0.2s ease;
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;
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 {
border-color: ${cssManager.bdTheme('#0069f2', '#0084ff')};
box-shadow: 0 0 0 2px ${cssManager.bdTheme('rgba(0, 105, 242, 0.1)', 'rgba(0, 132, 255, 0.2)')};
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)')};
}
.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;
opacity: 0.6;
opacity: 0.5;
}
.tag {
display: inline-flex;
align-items: center;
gap: 4px;
padding: 4px 8px;
background: ${cssManager.bdTheme('#e3f2fd', '#1e3a5f')};
color: ${cssManager.bdTheme('#1976d2', '#90caf9')};
border-radius: 16px;
font-size: 14px;
line-height: 1.2;
padding: 2px 8px;
background: ${cssManager.bdTheme('hsl(215 20.2% 65.1% / 0.2)', 'hsl(215 20.2% 35.1% / 0.2)')};
color: ${cssManager.bdTheme('hsl(215.3 25% 26.7%)', 'hsl(217.9 10.6% 74.9%)')};
border: 1px solid ${cssManager.bdTheme('hsl(215 20.2% 65.1% / 0.3)', 'hsl(215 20.2% 35.1% / 0.3)')};
border-radius: 4px;
font-size: 13px;
font-weight: 500;
line-height: 18px;
user-select: none;
animation: tagAppear 0.2s ease;
animation: tagAppear 0.15s cubic-bezier(0.4, 0, 0.2, 1);
}
@keyframes tagAppear {
from {
transform: scale(0.8);
transform: scale(0.95);
opacity: 0;
}
to {
@ -114,21 +121,23 @@ export class DeesInputTags extends DeesInputBase<DeesInputTags> {
display: flex;
align-items: center;
justify-content: center;
width: 16px;
height: 16px;
border-radius: 50%;
cursor: pointer;
transition: all 0.2s ease;
width: 14px;
height: 14px;
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 {
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 {
width: 12px;
height: 12px;
width: 10px;
height: 10px;
}
.tag-input {
@ -139,12 +148,13 @@ export class DeesInputTags extends DeesInputBase<DeesInputTags> {
outline: none;
font-size: 14px;
font-family: inherit;
color: ${cssManager.bdTheme('#333', '#fff')};
padding: 4px;
color: ${cssManager.bdTheme('hsl(0 0% 9%)', 'hsl(0 0% 95%)')};
padding: 2px 4px;
line-height: 20px;
}
.tag-input::placeholder {
color: ${cssManager.bdTheme('#999', '#666')};
color: ${cssManager.bdTheme('hsl(0 0% 63.9%)', 'hsl(0 0% 45.1%)')};
}
.tag-input:disabled {
@ -162,44 +172,64 @@ export class DeesInputTags extends DeesInputBase<DeesInputTags> {
left: 0;
right: 0;
margin-top: 4px;
background: ${cssManager.bdTheme('#ffffff', '#222222')};
border: 1px solid ${cssManager.bdTheme('#e0e0e0', '#333333')};
border-radius: 8px;
box-shadow: 0 4px 12px ${cssManager.bdTheme('rgba(0, 0, 0, 0.1)', 'rgba(0, 0, 0, 0.3)')};
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: 6px;
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;
overflow-y: auto;
z-index: 1000;
}
.suggestion {
padding: 8px 12px;
padding: 6px 10px;
cursor: pointer;
transition: all 0.2s ease;
color: ${cssManager.bdTheme('#333', '#fff')};
transition: all 0.15s ease;
font-size: 14px;
color: ${cssManager.bdTheme('hsl(0 0% 15%)', 'hsl(0 0% 90%)')};
}
.suggestion:hover,
.suggestion.highlighted {
background: ${cssManager.bdTheme('#f5f5f5', '#333333')};
.suggestion:hover {
background: ${cssManager.bdTheme('hsl(0 0% 95.1%)', 'hsl(0 0% 14.9%)')};
}
.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-message {
color: #d32f2f;
font-size: 12px;
margin-top: 4px;
min-height: 16px;
color: ${cssManager.bdTheme('hsl(0 72.2% 50.6%)', 'hsl(0 62.8% 30.6%)')};
font-size: 13px;
margin-top: 6px;
line-height: 1.5;
}
/* Description styles */
.description {
color: ${cssManager.bdTheme('#666', '#999')};
font-size: 12px;
margin-top: 4px;
color: ${cssManager.bdTheme('hsl(215.4 16.3% 56.9%)', 'hsl(215 20.2% 55.1%)')};
font-size: 13px;
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%)')};
}
`,
];

View File

@ -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 './dees-panel.js';
export const demoFunc = () => html`
<dees-demowrapper>
@ -14,36 +15,12 @@ export const demoFunc = () => html`
margin: 0 auto;
}
.demo-section {
background: #f8f9fa;
border-radius: 8px;
padding: 24px;
dees-panel {
margin-bottom: 24px;
}
@media (prefers-color-scheme: dark) {
.demo-section {
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;
}
dees-panel:last-child {
margin-bottom: 0;
}
.horizontal-group {
@ -64,14 +41,28 @@ export const demoFunc = () => html`
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>
<div class="demo-container">
<div class="demo-section">
<h3>Basic Text Inputs</h3>
<p>Standard text inputs with labels and descriptions</p>
<dees-panel .title=${'Basic Text Inputs'} .subtitle=${'Standard text inputs with labels and descriptions'}>
<dees-input-text
.label=${'Username'}
.value=${'johndoe'}
@ -91,12 +82,9 @@ export const demoFunc = () => html`
.value=${'secret123'}
.key=${'password'}
></dees-input-text>
</div>
<div class="demo-section">
<h3>Horizontal Layout</h3>
<p>Multiple inputs arranged horizontally for compact forms</p>
</dees-panel>
<dees-panel .title=${'Horizontal Layout'} .subtitle=${'Multiple inputs arranged horizontally for compact forms'}>
<div class="horizontal-group">
<dees-input-text
.label=${'First Name'}
@ -119,12 +107,9 @@ export const demoFunc = () => html`
.key=${'age'}
></dees-input-text>
</div>
</div>
<div class="demo-section">
<h3>Label Positions</h3>
<p>Different label positioning options for various layouts</p>
</dees-panel>
<dees-panel .title=${'Label Positions'} .subtitle=${'Different label positioning options for various layouts'}>
<dees-input-text
.label=${'Label on Top (Default)'}
.value=${'Standard layout'}
@ -150,12 +135,9 @@ export const demoFunc = () => html`
.labelPosition=${'left'}
></dees-input-text>
</div>
</div>
<div class="demo-section">
<h3>Validation & States</h3>
<p>Different validation states and input configurations</p>
</dees-panel>
<dees-panel .title=${'Validation & States'} .subtitle=${'Different validation states and input configurations'}>
<dees-input-text
.label=${'Required Field'}
.required=${true}
@ -174,12 +156,9 @@ export const demoFunc = () => html`
.validationText=${'Please enter a valid email address'}
.validationState=${'invalid'}
></dees-input-text>
</div>
<div class="demo-section">
<h3>Advanced Features</h3>
<p>Password visibility toggle and other advanced features</p>
</dees-panel>
<dees-panel .title=${'Advanced Features'} .subtitle=${'Password visibility toggle and other advanced features'}>
<dees-input-text
.label=${'Password with Toggle'}
.isPasswordBool=${true}
@ -193,7 +172,24 @@ export const demoFunc = () => html`
.value=${'sk-1234567890abcdef'}
.description=${'Keep this key secure and never share it'}
></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>
</dees-panel>
</div>
</dees-demowrapper>
`;

View File

@ -65,77 +65,126 @@ export class DeesInputText extends DeesInputBase {
:host {
position: relative;
z-index: auto;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif;
}
.maincontainer {
color: ${cssManager.bdTheme('#333', '#ccc')};
position: relative;
color: ${cssManager.bdTheme('hsl(0 0% 15%)', 'hsl(0 0% 90%)')};
}
input {
margin-top: 0px;
background: ${cssManager.bdTheme('#fafafa', '#222')};
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;
display: flex;
height: 40px;
width: 100%;
line-height: 36px;
transition: all 0.2s;
padding: 0 12px;
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;
font-size: 16px;
position: relative;
z-index: 2;
cursor: default;
cursor: text;
font-family: inherit;
color: ${cssManager.bdTheme('hsl(0 0% 9%)', 'hsl(0 0% 95%)')};
}
input:disabled {
background: ${cssManager.bdTheme('#ffffff00', '#11111100')};
border: 1px dashed ${cssManager.bdTheme('#666666', '#666666')};
color: #9b9b9e;
cursor: default;
input::placeholder {
color: ${cssManager.bdTheme('hsl(0 0% 63.9%)', 'hsl(0 0% 45.1%)')};
}
input:hover:not(:disabled):not(:focus) {
border-color: ${cssManager.bdTheme('hsl(0 0% 79.8%)', 'hsl(0 0% 20.9%)')};
}
input:focus {
outline: none;
border-bottom: 1px solid
${cssManager.bdTheme(colors.bright.blueActive, colors.dark.blueActive)};
cursor: text;
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)')};
}
input:hover {
filter: ${cssManager.bdTheme('brightness(0.95)', 'brightness(1.1)')};
input: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%)')};
color: ${cssManager.bdTheme('hsl(0 0% 63.9%)', 'hsl(0 0% 45.1%)')};
cursor: not-allowed;
opacity: 0.5;
}
/* Password toggle button */
.showPassword {
position: absolute;
bottom: 7px;
right: 10px;
border: 1px dashed #444;
border-radius: 7px;
padding: 4px 0px;
width: 40px;
z-index: 3;
text-align: center;
right: 1px;
top: 50%;
transform: translateY(-50%);
display: flex;
align-items: center;
justify-content: center;
width: 38px;
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 {
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 {
text-align: center;
padding: 6px 2px 2px 2px;
margin-top: -4px;
margin-top: 4px;
padding: 4px 8px;
font-size: 12px;
color: #fff;
background: #e4002b;
position: relative;
z-index: 1;
border-radius: 3px;
transition: all 0.2s;
font-weight: 500;
border-radius: 4px;
transition: all 0.2s ease;
overflow: hidden;
}
.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`
<style>
input {
font-family: ${this.isPasswordBool ? 'monospace' : 'Geist Sans'};
letter-spacing: ${this.isPasswordBool ? '1px' : 'normal'};
color: ${this.goBright ? '#333' : '#ccc'};
font-family: ${this.isPasswordBool ? 'SF Mono, Monaco, Consolas, Liberation Mono, Courier New, monospace' : 'inherit'};
letter-spacing: ${this.isPasswordBool ? '0.5px' : 'normal'};
padding-right: ${this.isPasswordBool ? '48px' : '12px'};
}
${this.validationText
? css`
.validationContainer {
height: 22px;
height: auto;
opacity: 1;
transform: translateY(0);
}
`
: css`
.validationContainer {
height: 4px;
padding: 2px !important;
height: 0;
padding: 0 !important;
opacity: 0;
transform: translateY(-4px);
}
`}
</style>
<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">
<input
type="${this.isPasswordBool && !this.showPasswordBool ? 'password' : 'text'}"
.value=${this.value}
@input="${this.updateValue}"
.disabled=${this.disabled}
placeholder="${this.label ? '' : 'Enter text...'}"
/>
<div class="validationContainer">${this.validationText}</div>
${this.isPasswordBool
? html`
<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>
`
: html``}
${this.validationText
? html`
<div class="validationContainer ${this.validationState || 'error'}">
${this.validationText}
</div>
`
: html`<div class="validationContainer"></div>`}
</div>
</div>
`;
@ -205,7 +263,6 @@ export class DeesInputText extends DeesInputBase {
public async togglePasswordView() {
this.showPasswordBool = !this.showPasswordBool;
console.log(`this.showPasswordBool is: ${this.showPasswordBool}`);
}
public async focus() {

View File

@ -32,20 +32,43 @@ export class DeesLabel extends DeesElement {
})
public description: string;
@property({
type: Boolean,
reflect: true,
})
public required: boolean = false;
public static styles = [
cssManager.defaultStyles,
css`
:host {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif;
}
.label {
color: ${cssManager.bdTheme('#333', '#ccc')};
display: inline-block;
color: ${cssManager.bdTheme('hsl(0 0% 15%)', 'hsl(0 0% 90%)')};
font-size: 14px;
margin-bottom: 8px;
font-weight: 500;
line-height: 1.5;
margin-bottom: 6px;
cursor: default;
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 {
display: inline-block;
font-size: 14px;
transform: translateY(1.5px);
font-size: 12px;
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`
<div class="label">
${this.label}
${this.required ? html`<span class="required">*</span>` : ''}
${this.description
? html`
<dees-icon .iconFA=${'circleInfo'}></dees-icon>
<dees-icon .iconName=${'lucideInfo'}></dees-icon>
<dees-speechbubble .text=${this.description}></dees-speechbubble>
`
: html``}

View File

@ -5,7 +5,7 @@ export const demoFunc = () => html`
${css`
.demo-background {
padding: 24px;
background: ${cssManager.bdTheme('#f0f0f0', '#0a0a0a')};
background: ${cssManager.bdTheme('hsl(0 0% 95%)', 'hsl(0 0% 5%)')};
min-height: 100vh;
}
@ -17,65 +17,156 @@ export const demoFunc = () => html`
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 {
display: grid;
grid-template-columns: 1fr 1fr;
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) {
.grid-layout {
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>
<div class="demo-background">
<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>It's perfect for creating sections in your application with proper spacing and borders.</p>
</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">
<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>
<dees-button>Action</dees-button>
<dees-button>Learn More</dees-button>
</dees-panel>
<dees-panel .title=${'Feature 2'}>
<p>Each panel maintains consistent spacing and styling.</p>
<dees-button>Another Action</dees-button>
<dees-panel .title=${'Quick Actions'} .subtitle=${'Common tasks'}>
<p>Each panel maintains consistent spacing and styling across your application.</p>
<dees-button>Get Started</dees-button>
</dees-panel>
</div>
<dees-panel .title=${'Complex Content'}>
<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>
<h2 class="section-title">Panel Variants</h2>
<dees-input-text .label=${'Example Input'} .description=${'Input inside a panel'}></dees-input-text>
<div style="margin-top: 16px;">
<dees-button>Submit</dees-button>
</div>
<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>
<p>Use <code>variant="default"</code> or omit the variant property.</p>
</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>
<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>
</div>
</div>
</div>
`;

View File

@ -8,6 +8,7 @@ import {
type TemplateResult,
} from '@design.estate/dees-element';
import { demoFunc } from './dees-panel.demo.js';
import { cssGeistFontFamily } from './00fonts.js';
declare global {
interface HTMLElementTagNameMap {
@ -25,35 +26,113 @@ export class DeesPanel extends DeesElement {
@property({ type: 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 = [
cssManager.defaultStyles,
css`
:host {
display: block;
background: ${cssManager.bdTheme('hsl(0 0% 100%)', 'hsl(215 20.2% 11.8%)')};
border-radius: 8px;
font-family: ${cssGeistFontFamily};
background: ${cssManager.bdTheme('hsl(0 0% 100%)', 'hsl(0 0% 3.9%)')};
border-radius: 6px;
padding: 24px;
border: 1px solid ${cssManager.bdTheme('hsl(214.3 31.8% 91.4%)', 'hsl(215 20.2% 16.8%)')};
box-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
border: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')};
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 {
margin: 0 0 8px 0;
margin: 0;
font-size: 18px;
font-weight: 600;
color: ${cssManager.bdTheme('hsl(215.3 25% 8.8%)', 'hsl(210 40% 98%)')};
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 {
margin: 0 0 16px 0;
margin: 4px 0 0 0;
font-size: 14px;
color: ${cssManager.bdTheme('hsl(215.4 16.3% 56.9%)', 'hsl(215 20.2% 55.1%)')};
letter-spacing: -0.01em;
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 {
color: ${cssManager.bdTheme('hsl(215.3 25% 8.8%)', 'hsl(210 40% 98%)')};
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 */
@ -64,16 +143,57 @@ export class DeesPanel extends DeesElement {
.content ::slotted(*:last-child) {
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 {
return html`
<div class="header">
${this.title ? html`<h3 class="title">${this.title}</h3>` : ''}
${this.subtitle ? html`<p class="subtitle">${this.subtitle}</p>` : ''}
</div>
<div class="content">
<slot></slot>
</div>
`;
}
public async firstUpdated() {
if (this.runAfterRender) {
await this.runAfterRender(this);
}
}
}

View 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>
`;

View 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
}));
}
}

View File

@ -46,6 +46,7 @@ export * from './dees-input-multitoggle.js';
export * from './dees-panel.js';
export * from './dees-pdf.js';
export * from './dees-searchbar.js';
export * from './dees-shopping-productcard.js';
export * from './dees-simple-appdash.js';
export * from './dees-simple-login.js';
export * from './dees-speechbubble.js';