Compare commits

..

14 Commits

Author SHA1 Message Date
46065b2424 1.10.8
Some checks failed
Default (tags) / security (push) Failing after 55s
Default (tags) / test (push) Failing after 17s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-06-27 21:19:43 +00:00
e76a6c3632 update 2025-06-27 21:19:14 +00:00
896bc2bbb1 update 2025-06-27 21:16:52 +00:00
296d254ba2 update 2025-06-27 21:07:47 +00:00
ecad05098f update 2025-06-27 21:05:28 +00:00
956964f5b9 update dees-chips 2025-06-27 21:01:12 +00:00
ed73e16bbb update dees-modal 2025-06-27 19:48:32 +00:00
7817b4a440 1.10.7
Some checks failed
Default (tags) / security (push) Failing after 54s
Default (tags) / test (push) Failing after 12s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-06-27 19:33:37 +00:00
03f25b7f10 update wysiwyg 2025-06-27 19:29:26 +00:00
24957f02d4 update wysiwyg 2025-06-27 19:25:34 +00:00
fe3cd0591f update 2025-06-27 18:38:39 +00:00
56f5f5887b update 2025-06-27 18:03:42 +00:00
2e0bf26301 update table 2025-06-27 17:50:54 +00:00
3d7f5253e8 update fonts 2025-06-27 17:32:01 +00:00
33 changed files with 2494 additions and 837 deletions

View File

@ -1,6 +1,6 @@
{ {
"name": "@design.estate/dees-catalog", "name": "@design.estate/dees-catalog",
"version": "1.10.6", "version": "1.10.8",
"private": false, "private": false,
"description": "A comprehensive library that provides dynamic web components for building sophisticated and modern web applications using JavaScript and TypeScript.", "description": "A comprehensive library that provides dynamic web components for building sophisticated and modern web applications using JavaScript and TypeScript.",
"main": "dist_ts_web/index.js", "main": "dist_ts_web/index.js",
@ -17,7 +17,7 @@
"dependencies": { "dependencies": {
"@design.estate/dees-domtools": "^2.3.3", "@design.estate/dees-domtools": "^2.3.3",
"@design.estate/dees-element": "^2.0.45", "@design.estate/dees-element": "^2.0.45",
"@design.estate/dees-wcctools": "^1.0.101", "@design.estate/dees-wcctools": "^1.1.0",
"@fortawesome/fontawesome-svg-core": "^6.7.2", "@fortawesome/fontawesome-svg-core": "^6.7.2",
"@fortawesome/free-brands-svg-icons": "^6.7.2", "@fortawesome/free-brands-svg-icons": "^6.7.2",
"@fortawesome/free-regular-svg-icons": "^6.7.2", "@fortawesome/free-regular-svg-icons": "^6.7.2",
@ -36,7 +36,7 @@
"apexcharts": "^4.7.0", "apexcharts": "^4.7.0",
"highlight.js": "11.11.1", "highlight.js": "11.11.1",
"ibantools": "^4.5.1", "ibantools": "^4.5.1",
"lucide": "^0.523.0", "lucide": "^0.525.0",
"monaco-editor": "^0.52.2", "monaco-editor": "^0.52.2",
"pdfjs-dist": "^4.10.38", "pdfjs-dist": "^4.10.38",
"xterm": "^5.3.0", "xterm": "^5.3.0",

22
pnpm-lock.yaml generated
View File

@ -15,8 +15,8 @@ importers:
specifier: ^2.0.45 specifier: ^2.0.45
version: 2.0.45 version: 2.0.45
'@design.estate/dees-wcctools': '@design.estate/dees-wcctools':
specifier: ^1.0.101 specifier: ^1.1.0
version: 1.0.101 version: 1.1.0
'@fortawesome/fontawesome-svg-core': '@fortawesome/fontawesome-svg-core':
specifier: ^6.7.2 specifier: ^6.7.2
version: 6.7.2 version: 6.7.2
@ -72,8 +72,8 @@ importers:
specifier: ^4.5.1 specifier: ^4.5.1
version: 4.5.1 version: 4.5.1
lucide: lucide:
specifier: ^0.523.0 specifier: ^0.525.0
version: 0.523.0 version: 0.525.0
monaco-editor: monaco-editor:
specifier: ^0.52.2 specifier: ^0.52.2
version: 0.52.2 version: 0.52.2
@ -323,8 +323,8 @@ packages:
'@design.estate/dees-element@2.0.45': '@design.estate/dees-element@2.0.45':
resolution: {integrity: sha512-dj8nOOtfwvqEtQceTXQQ5IEy75HIFZ+iuDxPeIynLedYpxtHPsxFrHW8IQ7/ad9MNvVO0kTnlwUOmkjylul+DA==} resolution: {integrity: sha512-dj8nOOtfwvqEtQceTXQQ5IEy75HIFZ+iuDxPeIynLedYpxtHPsxFrHW8IQ7/ad9MNvVO0kTnlwUOmkjylul+DA==}
'@design.estate/dees-wcctools@1.0.101': '@design.estate/dees-wcctools@1.1.0':
resolution: {integrity: sha512-6j2kGORf7egRkHGwQUNuxSdTe2+6y7eX3+dVomBLeWczH30KhPi1jJKINSt/MqkpB5i7o3kQwvvWA6JYBOjXcg==} resolution: {integrity: sha512-eniG2JsGgcVXQLkSE6M7azJ7av/UeTvvzhE6s3JbcIieHd589SCxQqF+BhlOyKqzJQ1n5jJ7KKdmhvQU5bbdtg==}
'@emnapi/core@1.4.3': '@emnapi/core@1.4.3':
resolution: {integrity: sha512-4m62DuCE07lw01soJwPiBGC0nAww0Q+RY70VZ+n49yDIO13yyinhbWCeNnaob0lakDtWQzSdtNWzJeOJt2ma+g==} resolution: {integrity: sha512-4m62DuCE07lw01soJwPiBGC0nAww0Q+RY70VZ+n49yDIO13yyinhbWCeNnaob0lakDtWQzSdtNWzJeOJt2ma+g==}
@ -3481,8 +3481,8 @@ packages:
resolution: {integrity: sha512-MhWWlVnuab1RG5/zMRRcVGXZLCXrZTgfwMikgzCegsPnG62yDQo5JnqKkrK4jO5iKqDAZGItAqN5CtKBCBWRUA==} resolution: {integrity: sha512-MhWWlVnuab1RG5/zMRRcVGXZLCXrZTgfwMikgzCegsPnG62yDQo5JnqKkrK4jO5iKqDAZGItAqN5CtKBCBWRUA==}
engines: {node: '>=16.14'} engines: {node: '>=16.14'}
lucide@0.523.0: lucide@0.525.0:
resolution: {integrity: sha512-tiIp5xEP4kpeulfT1J+a/NEaIZGT1k6RyMS3evQWfHRhJvR8uTat/+lN4wnX5qIexOwN02BhmcyMHBNwt+jkLA==} resolution: {integrity: sha512-sfehWlaE/7NVkcEQ4T9JD3eID8RNMIGJBBUq9wF3UFiJIrcMKRbU3g1KGfDk4svcW7yw8BtDLXaXo02scDtUYQ==}
make-dir@3.1.0: make-dir@3.1.0:
resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==} resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==}
@ -5588,7 +5588,7 @@ snapshots:
- supports-color - supports-color
- vue - vue
'@design.estate/dees-wcctools@1.0.101': '@design.estate/dees-wcctools@1.1.0':
dependencies: dependencies:
'@design.estate/dees-domtools': 2.3.3 '@design.estate/dees-domtools': 2.3.3
'@design.estate/dees-element': 2.0.45 '@design.estate/dees-element': 2.0.45
@ -5905,10 +5905,8 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- '@nuxt/kit' - '@nuxt/kit'
- '@swc/helpers' - '@swc/helpers'
- bufferutil
- react - react
- supports-color - supports-color
- utf-8-validate
- vue - vue
'@hapi/bourne@3.0.0': {} '@hapi/bourne@3.0.0': {}
@ -9564,7 +9562,7 @@ snapshots:
lru-cache@8.0.5: {} lru-cache@8.0.5: {}
lucide@0.523.0: {} lucide@0.525.0: {}
make-dir@3.1.0: make-dir@3.1.0:
dependencies: dependencies:

View File

@ -0,0 +1,35 @@
import { expect, tap } from '@git.zone/tstest/tapbundle';
import { DeesContextmenu } from '../ts_web/elements/dees-contextmenu.js';
import { demoFunc } from '../ts_web/elements/dees-contextmenu.demo.js';
tap.test('should render context menu demo', async () => {
// Create demo container
const demoContainer = document.createElement('div');
document.body.appendChild(demoContainer);
// Render the demo
const demoContent = demoFunc();
// Create a temporary element to hold the rendered template
const tempDiv = document.createElement('div');
tempDiv.innerHTML = demoContent.strings.join('');
// Check that panels are rendered
const panels = tempDiv.querySelectorAll('dees-panel');
expect(panels.length).toEqual(4);
// Check panel headings
expect(panels[0].getAttribute('heading')).toEqual('Basic Context Menu with Nested Submenus');
expect(panels[1].getAttribute('heading')).toEqual('Component-Specific Context Menu');
expect(panels[2].getAttribute('heading')).toEqual('Advanced Context Menu Example');
expect(panels[3].getAttribute('heading')).toEqual('Static Context Menu (Always Visible)');
// Check that static context menu exists
const staticMenu = tempDiv.querySelector('dees-contextmenu');
expect(staticMenu).toBeTruthy();
// Clean up
demoContainer.remove();
});
export default tap.start();

View File

@ -0,0 +1,93 @@
import { expect, tap } from '@git.zone/tstest/tapbundle';
import { DeesContextmenu } from '../ts_web/elements/dees-contextmenu.js';
tap.test('should close all parent menus when clicking action in nested submenu', async () => {
let actionCalled = false;
// Create a test element
const testDiv = document.createElement('div');
testDiv.style.width = '300px';
testDiv.style.height = '300px';
testDiv.style.background = '#f0f0f0';
testDiv.innerHTML = 'Right-click for nested menu test';
document.body.appendChild(testDiv);
// Simulate right-click to open context menu
const contextMenuEvent = new MouseEvent('contextmenu', {
clientX: 150,
clientY: 150,
bubbles: true,
cancelable: true
});
// Open context menu with nested structure
DeesContextmenu.openContextMenuWithOptions(contextMenuEvent, [
{
name: 'Parent Item',
iconName: 'folder',
action: async () => {}, // Parent items with submenus need an action
submenu: [
{
name: 'Child Item',
iconName: 'file',
action: async () => {
actionCalled = true;
console.log('Child action called');
}
},
{
name: 'Another Child',
iconName: 'fileText',
action: async () => console.log('Another child')
}
]
},
{
name: 'Regular Item',
iconName: 'box',
action: async () => console.log('Regular item')
}
]);
// Wait for main menu to appear
await new Promise(resolve => setTimeout(resolve, 150));
// Check main menu exists
const mainMenu = document.querySelector('dees-contextmenu');
expect(mainMenu).toBeInstanceOf(DeesContextmenu);
// Hover over "Parent Item" to trigger submenu
const parentItem = mainMenu!.shadowRoot!.querySelector('.menuitem');
expect(parentItem).toBeTruthy();
parentItem!.dispatchEvent(new MouseEvent('mouseenter', { bubbles: true }));
// Wait for submenu to appear
await new Promise(resolve => setTimeout(resolve, 300));
// Check submenu exists
const allMenus = document.querySelectorAll('dees-contextmenu');
expect(allMenus.length).toEqual(2); // Main menu and submenu
const submenu = allMenus[1];
expect(submenu).toBeTruthy();
// Click on "Child Item" in submenu
const childItem = submenu.shadowRoot!.querySelector('.menuitem');
expect(childItem).toBeTruthy();
childItem!.click();
// Wait for menus to close
await new Promise(resolve => setTimeout(resolve, 200));
// Verify action was called
expect(actionCalled).toEqual(true);
// Verify all menus are closed
const remainingMenus = document.querySelectorAll('dees-contextmenu');
expect(remainingMenus.length).toEqual(0);
// Clean up
testDiv.remove();
});
export default tap.start();

View File

@ -0,0 +1,71 @@
import { expect, tap } from '@git.zone/tstest/tapbundle';
import { DeesContextmenu } from '../ts_web/elements/dees-contextmenu.js';
import { DeesElement, customElement, html } from '@design.estate/dees-element';
// Create a test element with shadow DOM
@customElement('test-shadow-element')
class TestShadowElement extends DeesElement {
public getContextMenuItems() {
return [
{ name: 'Shadow Item 1', iconName: 'box', action: async () => console.log('Shadow 1') },
{ name: 'Shadow Item 2', iconName: 'package', action: async () => console.log('Shadow 2') }
];
}
render() {
return html`
<div style="padding: 40px; background: #eee; border-radius: 8px;">
<h3>Shadow DOM Content</h3>
<p>Right-click anywhere inside this shadow DOM</p>
</div>
`;
}
}
tap.test('should show context menu when right-clicking inside shadow DOM', async () => {
// Create the shadow DOM element
const shadowElement = document.createElement('test-shadow-element');
document.body.appendChild(shadowElement);
// Wait for element to be ready
await shadowElement.updateComplete;
// Get the content inside shadow DOM
const shadowContent = shadowElement.shadowRoot!.querySelector('div');
expect(shadowContent).toBeTruthy();
// Simulate right-click on content inside shadow DOM
const contextMenuEvent = new MouseEvent('contextmenu', {
clientX: 100,
clientY: 100,
bubbles: true,
cancelable: true,
composed: true // Important for shadow DOM
});
shadowContent!.dispatchEvent(contextMenuEvent);
// Wait for context menu to appear
await new Promise(resolve => setTimeout(resolve, 100));
// Check if context menu is created
const contextMenu = document.querySelector('dees-contextmenu');
expect(contextMenu).toBeInstanceOf(DeesContextmenu);
// Check if menu items from shadow element are rendered
const menuItems = contextMenu!.shadowRoot!.querySelectorAll('.menuitem');
expect(menuItems.length).toBeGreaterThanOrEqual(2);
// Check menu item text
const menuTexts = Array.from(menuItems).map(item =>
item.querySelector('.menuitem-text')?.textContent
);
expect(menuTexts).toContain('Shadow Item 1');
expect(menuTexts).toContain('Shadow Item 2');
// Clean up
contextMenu!.remove();
shadowElement.remove();
});
export default tap.start();

View File

@ -0,0 +1,77 @@
import { expect, tap } from '@git.zone/tstest/tapbundle';
import { DeesContextmenu } from '../ts_web/elements/dees-contextmenu.js';
tap.test('should show context menu with nested submenu', async () => {
// Create a test element with context menu items
const testDiv = document.createElement('div');
testDiv.style.width = '200px';
testDiv.style.height = '200px';
testDiv.style.background = '#eee';
testDiv.innerHTML = 'Right-click me';
// Add getContextMenuItems method
(testDiv as any).getContextMenuItems = () => {
return [
{
name: 'Change Type',
iconName: 'type',
submenu: [
{ name: 'Paragraph', iconName: 'text', action: () => console.log('Paragraph') },
{ name: 'Heading 1', iconName: 'heading1', action: () => console.log('Heading 1') },
{ name: 'Heading 2', iconName: 'heading2', action: () => console.log('Heading 2') },
{ divider: true },
{ name: 'Code Block', iconName: 'fileCode', action: () => console.log('Code') },
{ name: 'Quote', iconName: 'quote', action: () => console.log('Quote') }
]
},
{ divider: true },
{
name: 'Delete',
iconName: 'trash2',
action: () => console.log('Delete')
}
];
};
document.body.appendChild(testDiv);
// Simulate right-click
const contextMenuEvent = new MouseEvent('contextmenu', {
clientX: 100,
clientY: 100,
bubbles: true,
cancelable: true
});
testDiv.dispatchEvent(contextMenuEvent);
// Wait for context menu to appear
await new Promise(resolve => setTimeout(resolve, 100));
// Check if context menu is created
const contextMenu = document.querySelector('dees-contextmenu');
expect(contextMenu).toBeInstanceOf(DeesContextmenu);
// Check if menu items are rendered
const menuItems = contextMenu!.shadowRoot!.querySelectorAll('.menuitem');
expect(menuItems.length).toEqual(2); // "Change Type" and "Delete"
// Hover over "Change Type" to trigger submenu
const changeTypeItem = menuItems[0] as HTMLElement;
changeTypeItem.dispatchEvent(new MouseEvent('mouseenter', { bubbles: true }));
// Wait for submenu to appear
await new Promise(resolve => setTimeout(resolve, 300));
// Check if submenu is created
const submenus = document.querySelectorAll('dees-contextmenu');
expect(submenus.length).toEqual(2); // Main menu and submenu
// Clean up
contextMenu!.remove();
const submenu = submenus[1];
if (submenu) submenu.remove();
testDiv.remove();
});
export default tap.start();

View File

@ -0,0 +1,98 @@
import { expect, tap } from '@git.zone/tstest/tapbundle';
import { DeesInputWysiwyg } from '../ts_web/elements/wysiwyg/dees-input-wysiwyg.js';
import { DeesContextmenu } from '../ts_web/elements/dees-contextmenu.js';
tap.test('should change block type via context menu', async () => {
// Create WYSIWYG editor with a paragraph
const wysiwygEditor = new DeesInputWysiwyg();
wysiwygEditor.value = '<p>This is a test paragraph</p>';
document.body.appendChild(wysiwygEditor);
// Wait for editor to be ready
await wysiwygEditor.updateComplete;
await new Promise(resolve => setTimeout(resolve, 100));
// Get the first block
const firstBlock = wysiwygEditor.blocks[0];
expect(firstBlock.type).toEqual('paragraph');
// Get the block element
const firstBlockWrapper = wysiwygEditor.shadowRoot!.querySelector('.block-wrapper');
expect(firstBlockWrapper).toBeTruthy();
const blockComponent = firstBlockWrapper!.querySelector('dees-wysiwyg-block') as any;
expect(blockComponent).toBeTruthy();
await blockComponent.updateComplete;
// Get the editable content inside the block's shadow DOM
const editableBlock = blockComponent.shadowRoot!.querySelector('.block');
expect(editableBlock).toBeTruthy();
// Simulate right-click on the editable block
const contextMenuEvent = new MouseEvent('contextmenu', {
clientX: 200,
clientY: 200,
bubbles: true,
cancelable: true,
composed: true
});
editableBlock!.dispatchEvent(contextMenuEvent);
// Wait for context menu to appear
await new Promise(resolve => setTimeout(resolve, 100));
// Check if context menu is created
const contextMenu = document.querySelector('dees-contextmenu');
expect(contextMenu).toBeInstanceOf(DeesContextmenu);
// Find "Change Type" menu item
const menuItems = Array.from(contextMenu!.shadowRoot!.querySelectorAll('.menuitem'));
const changeTypeItem = menuItems.find(item =>
item.querySelector('.menuitem-text')?.textContent?.trim() === 'Change Type'
);
expect(changeTypeItem).toBeTruthy();
// Hover over "Change Type" to trigger submenu
changeTypeItem!.dispatchEvent(new MouseEvent('mouseenter', { bubbles: true }));
// Wait for submenu to appear
await new Promise(resolve => setTimeout(resolve, 300));
// Check if submenu is created
const allMenus = document.querySelectorAll('dees-contextmenu');
expect(allMenus.length).toEqual(2);
const submenu = allMenus[1];
const submenuItems = Array.from(submenu.shadowRoot!.querySelectorAll('.menuitem'));
// Find "Heading 1" option
const heading1Item = submenuItems.find(item =>
item.querySelector('.menuitem-text')?.textContent?.trim() === 'Heading 1'
);
expect(heading1Item).toBeTruthy();
// Click on "Heading 1"
(heading1Item as HTMLElement).click();
// Wait for menu to close and block to update
await new Promise(resolve => setTimeout(resolve, 300));
// Verify block type has changed
expect(wysiwygEditor.blocks[0].type).toEqual('heading-1');
// Verify DOM has been updated
const updatedBlockComponent = wysiwygEditor.shadowRoot!
.querySelector('.block-wrapper')!
.querySelector('dees-wysiwyg-block') as any;
await updatedBlockComponent.updateComplete;
const updatedBlock = updatedBlockComponent.shadowRoot!.querySelector('.block');
expect(updatedBlock?.classList.contains('heading-1')).toEqual(true);
// Clean up
wysiwygEditor.remove();
});
export default tap.start();

View File

@ -0,0 +1,68 @@
import { expect, tap } from '@git.zone/tstest/tapbundle';
import { DeesInputWysiwyg } from '../ts_web/elements/wysiwyg/dees-input-wysiwyg.js';
import { DeesContextmenu } from '../ts_web/elements/dees-contextmenu.js';
tap.test('should show context menu on WYSIWYG blocks', async () => {
// Create WYSIWYG editor
const wysiwygEditor = new DeesInputWysiwyg();
wysiwygEditor.value = '<p>Test paragraph</p><h1>Test heading</h1>';
document.body.appendChild(wysiwygEditor);
// Wait for editor to be ready
await wysiwygEditor.updateComplete;
await new Promise(resolve => setTimeout(resolve, 100));
// Get the first block element
const firstBlockWrapper = wysiwygEditor.shadowRoot!.querySelector('.block-wrapper');
expect(firstBlockWrapper).toBeTruthy();
const blockComponent = firstBlockWrapper!.querySelector('dees-wysiwyg-block') as any;
expect(blockComponent).toBeTruthy();
// Wait for block to be ready
await blockComponent.updateComplete;
// Get the editable content inside the block's shadow DOM
const editableBlock = blockComponent.shadowRoot!.querySelector('.block');
expect(editableBlock).toBeTruthy();
// Simulate right-click on the editable block
const contextMenuEvent = new MouseEvent('contextmenu', {
clientX: 200,
clientY: 200,
bubbles: true,
cancelable: true,
composed: true // Important for shadow DOM
});
editableBlock!.dispatchEvent(contextMenuEvent);
// Wait for context menu to appear
await new Promise(resolve => setTimeout(resolve, 100));
// Check if context menu is created
const contextMenu = document.querySelector('dees-contextmenu');
expect(contextMenu).toBeInstanceOf(DeesContextmenu);
// Check if menu items from WYSIWYG block are rendered
const menuItems = contextMenu!.shadowRoot!.querySelectorAll('.menuitem');
const menuTexts = Array.from(menuItems).map(item =>
item.querySelector('.menuitem-text')?.textContent?.trim()
);
// Should have "Change Type" and "Delete Block" items
expect(menuTexts).toContain('Change Type');
expect(menuTexts).toContain('Delete Block');
// Check if "Change Type" has submenu indicator
const changeTypeItem = Array.from(menuItems).find(item =>
item.querySelector('.menuitem-text')?.textContent?.trim() === 'Change Type'
);
expect(changeTypeItem?.classList.contains('has-submenu')).toEqual(true);
// Clean up
contextMenu!.remove();
wysiwygEditor.remove();
});
export default tap.start();

View File

@ -26,6 +26,22 @@ export const monoFontFamily = `'${intelOneMonoFont}', 'SF Mono', 'Monaco', 'Inco
export const cssGeistFontFamily = unsafeCSS(geistFontFamily); export const cssGeistFontFamily = unsafeCSS(geistFontFamily);
export const cssMonoFontFamily = unsafeCSS(monoFontFamily); export const cssMonoFontFamily = unsafeCSS(monoFontFamily);
/**
* Cal Sans font for headings - Display font
* May need to be loaded separately
*/
export const calSansFont = 'Cal Sans';
export const calSansFontFamily = `'${calSansFont}', ${geistFontFamily}`;
export const cssCalSansFontFamily = unsafeCSS(calSansFontFamily);
/**
* Roboto Slab font for special content - Serif font
* May need to be loaded separately
*/
export const robotoSlabFont = 'Roboto Slab';
export const robotoSlabFontFamily = `'${robotoSlabFont}', Georgia, serif`;
export const cssRobotoSlabFontFamily = unsafeCSS(robotoSlabFontFamily);
/** /**
* Base font styles that can be applied to components * Base font styles that can be applied to components
*/ */

View File

@ -1,41 +1,112 @@
import { html } from '@design.estate/dees-element'; import { html, cssManager } from '@design.estate/dees-element';
export const demoFunc = () => html` export const demoFunc = () => html`
<style> <style>
.demoContainer { .demoContainer {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; gap: 32px;
justify-content: center; padding: 48px;
height: 100%; background: ${cssManager.bdTheme('#f8f9fa', '#0a0a0a')};
background: #222; min-height: 100vh;
}
.section {
background: ${cssManager.bdTheme('#ffffff', '#18181b')};
border: 1px solid ${cssManager.bdTheme('#e5e7eb', '#27272a')};
border-radius: 8px;
padding: 24px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
.section-title {
font-size: 18px;
font-weight: 600;
margin-bottom: 16px;
color: ${cssManager.bdTheme('#09090b', '#fafafa')};
}
.section-description {
font-size: 14px;
color: ${cssManager.bdTheme('#71717a', '#a1a1aa')};
margin-bottom: 16px;
} }
</style> </style>
<div class="demoContainer"> <div class="demoContainer">
<div class="section">
<div class="section-title">Non-Selectable Chips</div>
<div class="section-description">Basic chips without selection capability. Use for display-only tags.</div>
<dees-chips <dees-chips
selectionMode="none" selectionMode="none"
.selectableChips=${[ .selectableChips=${[
{ key: 'account1', value: 'Payment Account 1' }, { key: 'status', value: 'Active' },
{ key: 'account2', value: 'PaymentAccount2' }, { key: 'tier', value: 'Premium' },
{ key: 'account3', value: 'Payment Account 3' }, { key: 'region', value: 'EU-West' },
{ key: 'type', value: 'Enterprise' },
]} ]}
></dees-chips> ></dees-chips>
</div>
<div class="section">
<div class="section-title">Single Selection Chips</div>
<div class="section-description">Click to select one chip at a time. Useful for filters and options.</div>
<dees-chips
selectionMode="single"
.selectableChips=${[
{ key: 'all', value: 'All Projects' },
{ key: 'active', value: 'Active' },
{ key: 'archived', value: 'Archived' },
{ key: 'drafts', value: 'Drafts' },
]}
></dees-chips>
</div>
<div class="section">
<div class="section-title">Multiple Selection Chips</div>
<div class="section-description">Select multiple chips simultaneously. Great for tag selection.</div>
<dees-chips
selectionMode="multiple"
.selectableChips=${[
{ key: 'js', value: 'JavaScript' },
{ key: 'ts', value: 'TypeScript' },
{ key: 'react', value: 'React' },
{ key: 'vue', value: 'Vue' },
{ key: 'angular', value: 'Angular' },
{ key: 'node', value: 'Node.js' },
]}
></dees-chips>
</div>
<div class="section">
<div class="section-title">Removable Chips with Keys</div>
<div class="section-description">Chips with remove buttons and key-value pairs. Perfect for dynamic lists.</div>
<dees-chips <dees-chips
selectionMode="single" selectionMode="single"
chipsAreRemovable chipsAreRemovable
.selectableChips=${[ .selectableChips=${[
{ key: 'account1', value: 'Payment Account 1' }, { key: 'env', value: 'Production' },
{ key: 'account2', value: 'PaymentAccount2' }, { key: 'version', value: '2.4.1' },
{ key: 'account3', value: 'Payment Account 3' }, { key: 'branch', value: 'main' },
]} { key: 'author', value: 'John Doe' },
></dees-chips>
<dees-chips
selectionMode="multiple"
.selectableChips=${[
{ key: 'account1', value: 'Payment Account 1' },
{ key: 'account2', value: 'PaymentAccount2' },
{ key: 'account3', value: 'Payment Account 3' },
]} ]}
></dees-chips> ></dees-chips>
</div> </div>
<div class="section">
<div class="section-title">Mixed Content Example</div>
<div class="section-description">Combining different chip types for complex UIs.</div>
<dees-chips
selectionMode="multiple"
chipsAreRemovable
.selectableChips=${[
{ key: 'priority', value: 'High' },
{ key: 'status', value: 'In Progress' },
{ key: 'bug', value: 'Bug' },
{ key: 'feature', value: 'Feature' },
{ key: 'sprint', value: 'Sprint 23' },
{ key: 'assignee', value: 'Alice' },
]}
></dees-chips>
</div>
</div>
`; `;

View File

@ -60,52 +60,93 @@ export class DeesChips extends DeesElement {
.mainbox { .mainbox {
user-select: none; user-select: none;
display: flex;
flex-wrap: wrap;
gap: 8px;
} }
.chip { .chip {
border-top: ${cssManager.bdTheme('1px solid #CCC', '1px solid #444')}; background: ${cssManager.bdTheme('#f4f4f5', '#27272a')};
background: #333333; border: 1px solid ${cssManager.bdTheme('#e5e7eb', '#3f3f46')};
display: inline-flex; display: inline-flex;
height: 20px; align-items: center;
line-height: 20px; height: 32px;
padding: 0px 8px; padding: 0px 12px;
font-size: 12px; font-size: 14px;
color: #fff; font-weight: 500;
border-radius: 40px; color: ${cssManager.bdTheme('#09090b', '#fafafa')};
margin-right: 4px; border-radius: 6px;
margin-bottom: 4px;
position: relative; position: relative;
overflow: hidden; cursor: pointer;
box-shadow: 0px 1px 2px rgba(0, 0, 0, 0.3); transition: all 0.15s ease;
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
} }
.chip:hover { .chip:hover {
background: #666666; background: ${cssManager.bdTheme('#e5e7eb', '#3f3f46')};
border-color: ${cssManager.bdTheme('#d1d5db', '#52525b')};
}
.chip:active {
transform: scale(0.98);
} }
.chip.selected { .chip.selected {
background: #00a3ff; background: ${cssManager.bdTheme('#3b82f6', '#3b82f6')};
border-color: ${cssManager.bdTheme('#3b82f6', '#3b82f6')};
color: #ffffff;
}
.chip.selected:hover {
background: ${cssManager.bdTheme('#2563eb', '#2563eb')};
border-color: ${cssManager.bdTheme('#2563eb', '#2563eb')};
} }
.chipKey { .chipKey {
background: rgba(0, 0, 0, 0.3); background: ${cssManager.bdTheme('rgba(0, 0, 0, 0.06)', 'rgba(255, 255, 255, 0.1)')};
height: 100%; height: 20px;
display: inline-block; line-height: 20px;
display: inline-flex;
align-items: center;
margin-left: -8px; margin-left: -8px;
padding-left: 8px; padding: 0px 8px;
padding-right: 8px;
margin-right: 8px; margin-right: 8px;
border-radius: 4px;
font-size: 12px;
font-weight: 600;
color: ${cssManager.bdTheme('#71717a', '#a1a1aa')};
}
.chip.selected .chipKey {
background: rgba(255, 255, 255, 0.2);
color: rgba(255, 255, 255, 0.9);
} }
dees-icon { dees-icon {
padding: 0px 6px 0px 4px; display: flex;
margin-left: 4px; align-items: center;
margin-right: -8px; justify-content: center;
background: rgba(0, 0, 0, 0.3); width: 16px;
height: 16px;
margin-left: 8px;
margin-right: -6px;
border-radius: 3px;
transition: all 0.15s ease;
color: ${cssManager.bdTheme('#71717a', '#a1a1aa')};
}
.chip.selected dees-icon {
color: rgba(255, 255, 255, 0.8);
} }
dees-icon:hover { dees-icon:hover {
background: #e4002b; background: ${cssManager.bdTheme('rgba(0, 0, 0, 0.1)', 'rgba(255, 255, 255, 0.1)')};
color: ${cssManager.bdTheme('#ef4444', '#ef4444')};
}
.chip.selected dees-icon:hover {
background: rgba(255, 255, 255, 0.2);
color: #ffffff;
} }
`, `,
]; ];
@ -139,20 +180,26 @@ export class DeesChips extends DeesElement {
} }
public async firstUpdated() { public async firstUpdated() {
if (!this.textContent) { // Component initialized
this.textContent = 'Button';
this.performUpdate();
}
} }
private isSelected(chip: Tag): boolean { private isSelected(chip: Tag): boolean {
if (this.selectionMode === 'single') { if (this.selectionMode === 'single') {
return this.selectedChip?.key === chip.key; return this.selectedChip ? this.isSameChip(this.selectedChip, chip) : false;
} else { } else {
return this.selectedChips.some((selected) => selected.key === chip.key); return this.selectedChips.some((selected) => this.isSameChip(selected, chip));
} }
} }
private isSameChip(chip1: Tag, chip2: Tag): boolean {
// If both have keys, compare by key
if (chip1.key && chip2.key) {
return chip1.key === chip2.key;
}
// Otherwise compare by value (and key if present)
return chip1.value === chip2.value && chip1.key === chip2.key;
}
public async selectChip(chip: Tag) { public async selectChip(chip: Tag) {
if (this.selectionMode === 'none') { if (this.selectionMode === 'none') {
return; return;
@ -168,7 +215,7 @@ export class DeesChips extends DeesElement {
} }
} else if (this.selectionMode === 'multiple') { } else if (this.selectionMode === 'multiple') {
if (this.isSelected(chip)) { if (this.isSelected(chip)) {
this.selectedChips = this.selectedChips.filter((selected) => selected.key !== chip.key); this.selectedChips = this.selectedChips.filter((selected) => !this.isSameChip(selected, chip));
} else { } else {
this.selectedChips = [...this.selectedChips, chip]; this.selectedChips = [...this.selectedChips, chip];
} }
@ -179,13 +226,13 @@ export class DeesChips extends DeesElement {
public removeChip(chipToRemove: Tag): void { public removeChip(chipToRemove: Tag): void {
// Remove the chip from selectableChips // Remove the chip from selectableChips
this.selectableChips = this.selectableChips.filter((chip) => chip.key !== chipToRemove.key); this.selectableChips = this.selectableChips.filter((chip) => !this.isSameChip(chip, chipToRemove));
// Remove the chip from selectedChips if present // Remove the chip from selectedChips if present
this.selectedChips = this.selectedChips.filter((chip) => chip.key !== chipToRemove.key); this.selectedChips = this.selectedChips.filter((chip) => !this.isSameChip(chip, chipToRemove));
// If the removed chip was the selectedChip, set selectedChip to null // If the removed chip was the selectedChip, set selectedChip to null
if (this.selectedChip && this.selectedChip.key === chipToRemove.key) { if (this.selectedChip && this.isSameChip(this.selectedChip, chipToRemove)) {
this.selectedChip = null; this.selectedChip = null;
} }

View File

@ -13,139 +13,203 @@ export const demoFunc = () => html`
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 20px; gap: 20px;
padding: 40px; padding: 20px;
background: #f5f5f5;
min-height: 400px; min-height: 400px;
} }
.demo-area { .demo-area {
background: white;
padding: 40px; padding: 40px;
border-radius: 8px; border-radius: 8px;
border: 1px solid #e0e0e0;
text-align: center; text-align: center;
cursor: context-menu; cursor: context-menu;
transition: background 0.2s;
}
.demo-area:hover {
background: rgba(0, 0, 0, 0.02);
} }
</style> </style>
<div class="demo-container"> <div class="demo-container">
<dees-panel heading="Basic Context Menu with Nested Submenus">
<div class="demo-area" @contextmenu=${(eventArg: MouseEvent) => { <div class="demo-area" @contextmenu=${(eventArg: MouseEvent) => {
DeesContextmenu.openContextMenuWithOptions(eventArg, [ DeesContextmenu.openContextMenuWithOptions(eventArg, [
{ {
name: 'Cut', name: 'File',
iconName: 'scissors', iconName: 'fileText',
shortcut: 'Cmd+X', action: async () => {}, // Parent items with submenus still need an action
action: async () => { submenu: [
console.log('Cut action'); { name: 'New', iconName: 'filePlus', shortcut: 'Cmd+N', action: async () => console.log('New file') },
}, { name: 'Open', iconName: 'folderOpen', shortcut: 'Cmd+O', action: async () => console.log('Open file') },
}, { name: 'Save', iconName: 'save', shortcut: 'Cmd+S', action: async () => console.log('Save') },
{
name: 'Copy',
iconName: 'copy',
shortcut: 'Cmd+C',
action: async () => {
console.log('Copy action');
},
},
{
name: 'Paste',
iconName: 'clipboard',
shortcut: 'Cmd+V',
action: async () => {
console.log('Paste action');
},
},
{ divider: true }, { divider: true },
{ name: 'Export as PDF', iconName: 'download', action: async () => console.log('Export PDF') },
{ name: 'Export as HTML', iconName: 'code', action: async () => console.log('Export HTML') },
]
},
{ {
name: 'Delete', name: 'Edit',
iconName: 'trash2', iconName: 'edit3',
action: async () => { action: async () => {}, // Parent items with submenus still need an action
console.log('Delete action'); submenu: [
}, { name: 'Cut', iconName: 'scissors', shortcut: 'Cmd+X', action: async () => console.log('Cut') },
}, { name: 'Copy', iconName: 'copy', shortcut: 'Cmd+C', action: async () => console.log('Copy') },
{ name: 'Paste', iconName: 'clipboard', shortcut: 'Cmd+V', action: async () => console.log('Paste') },
{ divider: true }, { divider: true },
{ { name: 'Find', iconName: 'search', shortcut: 'Cmd+F', action: async () => console.log('Find') },
name: 'Select All', { name: 'Replace', iconName: 'repeat', shortcut: 'Cmd+H', action: async () => console.log('Replace') },
shortcut: 'Cmd+A', ]
action: async () => {
console.log('Select All action');
},
},
]);
}}>
<h3>Right-click anywhere in this area</h3>
<p>A context menu will appear with various options</p>
</div>
<dees-button @contextmenu=${(eventArg: MouseEvent) => {
DeesContextmenu.openContextMenuWithOptions(eventArg, [
{
name: 'Button Action 1',
iconName: 'play',
action: async () => {
console.log('Button action 1');
},
}, },
{ {
name: 'Button Action 2', name: 'View',
iconName: 'pause', iconName: 'eye',
action: async () => { action: async () => {}, // Parent items with submenus still need an action
console.log('Button action 2'); submenu: [
}, { name: 'Zoom In', iconName: 'zoomIn', shortcut: 'Cmd++', action: async () => console.log('Zoom in') },
}, { name: 'Zoom Out', iconName: 'zoomOut', shortcut: 'Cmd+-', action: async () => console.log('Zoom out') },
{ { name: 'Reset Zoom', iconName: 'maximize2', shortcut: 'Cmd+0', action: async () => console.log('Reset zoom') },
name: 'Disabled Action', { divider: true },
iconName: 'ban', { name: 'Full Screen', iconName: 'maximize', shortcut: 'F11', action: async () => console.log('Full screen') },
disabled: true, ]
action: async () => {
console.log('This should not run');
},
}, },
{ divider: true }, { divider: true },
{ {
name: 'Settings', name: 'Settings',
iconName: 'settings', iconName: 'settings',
action: async () => { action: async () => console.log('Settings')
console.log('Settings');
}, },
{
name: 'Help',
iconName: 'helpCircle',
action: async () => {}, // Parent items with submenus still need an action
submenu: [
{ name: 'Documentation', iconName: 'book', action: async () => console.log('Documentation') },
{ name: 'Keyboard Shortcuts', iconName: 'keyboard', action: async () => console.log('Shortcuts') },
{ divider: true },
{ name: 'About', iconName: 'info', action: async () => console.log('About') },
]
}
]);
}}>
<h3>Right-click anywhere in this area</h3>
<p>A context menu with nested submenus will appear</p>
</div>
</dees-panel>
<dees-panel heading="Component-Specific Context Menu">
<dees-button style="margin: 20px;" @contextmenu=${(eventArg: MouseEvent) => {
DeesContextmenu.openContextMenuWithOptions(eventArg, [
{
name: 'Button Actions',
iconName: 'mousePointer',
action: async () => {}, // Parent items with submenus still need an action
submenu: [
{ name: 'Click', iconName: 'mouse', action: async () => console.log('Click action') },
{ name: 'Double Click', iconName: 'zap', action: async () => console.log('Double click') },
{ name: 'Long Press', iconName: 'clock', action: async () => console.log('Long press') },
]
},
{
name: 'Button State',
iconName: 'toggleLeft',
action: async () => {}, // Parent items with submenus still need an action
submenu: [
{ name: 'Enable', iconName: 'checkCircle', action: async () => console.log('Enable') },
{ name: 'Disable', iconName: 'xCircle', action: async () => console.log('Disable') },
{ divider: true },
{ name: 'Show', iconName: 'eye', action: async () => console.log('Show') },
{ name: 'Hide', iconName: 'eyeOff', action: async () => console.log('Hide') },
]
},
{ divider: true },
{
name: 'Disabled Action',
iconName: 'ban',
disabled: true,
action: async () => console.log('This should not run'),
},
{
name: 'Properties',
iconName: 'settings',
action: async () => console.log('Button properties'),
}, },
]); ]);
}}>Right-click on this button for a different menu</dees-button> }}>Right-click on this button</dees-button>
</dees-panel>
<div style="margin-top: 20px;"> <dees-panel heading="Advanced Context Menu Example">
<h4>Static Context Menu (always visible):</h4> <div class="demo-area" @contextmenu=${(eventArg: MouseEvent) => {
DeesContextmenu.openContextMenuWithOptions(eventArg, [
{
name: 'Format',
iconName: 'type',
action: async () => {}, // Parent items with submenus still need an action
submenu: [
{ name: 'Bold', iconName: 'bold', shortcut: 'Cmd+B', action: async () => console.log('Bold') },
{ name: 'Italic', iconName: 'italic', shortcut: 'Cmd+I', action: async () => console.log('Italic') },
{ name: 'Underline', iconName: 'underline', shortcut: 'Cmd+U', action: async () => console.log('Underline') },
{ divider: true },
{ name: 'Font Size', iconName: 'type', action: async () => console.log('Font size menu') },
{ name: 'Font Color', iconName: 'palette', action: async () => console.log('Font color menu') },
]
},
{
name: 'Transform',
iconName: 'shuffle',
action: async () => {}, // Parent items with submenus still need an action
submenu: [
{ name: 'To Uppercase', iconName: 'arrowUp', action: async () => console.log('Uppercase') },
{ name: 'To Lowercase', iconName: 'arrowDown', action: async () => console.log('Lowercase') },
{ name: 'Capitalize', iconName: 'type', action: async () => console.log('Capitalize') },
]
},
{ divider: true },
{
name: 'Delete',
iconName: 'trash2',
action: async () => console.log('Delete')
}
]);
}}>
<h3>Advanced Nested Menu Example</h3>
<p>This shows deeply nested submenus and various formatting options</p>
</div>
</dees-panel>
<dees-panel heading="Static Context Menu (Always Visible)">
<dees-contextmenu <dees-contextmenu
class="withMargin" class="withMargin"
.menuItems=${[ .menuItems=${[
{ {
name: 'New File', name: 'Project',
iconName: 'filePlus', iconName: 'folder',
shortcut: 'Cmd+N', action: async () => {}, // Parent items with submenus still need an action
action: async () => console.log('New file'), submenu: [
{ name: 'New Project', iconName: 'folderPlus', shortcut: 'Cmd+Shift+N', action: async () => console.log('New project') },
{ name: 'Open Project', iconName: 'folderOpen', shortcut: 'Cmd+Shift+O', action: async () => console.log('Open project') },
{ divider: true },
{ name: 'Recent Projects', iconName: 'clock', action: async () => {}, submenu: [
{ name: 'Project Alpha', action: async () => console.log('Open Alpha') },
{ name: 'Project Beta', action: async () => console.log('Open Beta') },
{ name: 'Project Gamma', action: async () => console.log('Open Gamma') },
]},
]
}, },
{ {
name: 'Open File', name: 'Tools',
iconName: 'folderOpen', iconName: 'tool',
shortcut: 'Cmd+O', action: async () => {}, // Parent items with submenus still need an action
action: async () => console.log('Open file'), submenu: [
}, { name: 'Terminal', iconName: 'terminal', shortcut: 'Cmd+T', action: async () => console.log('Terminal') },
{ { name: 'Console', iconName: 'monitor', shortcut: 'Cmd+K', action: async () => console.log('Console') },
name: 'Save', { divider: true },
iconName: 'save', { name: 'Extensions', iconName: 'package', action: async () => console.log('Extensions') },
shortcut: 'Cmd+S', ]
action: async () => console.log('Save'),
}, },
{ divider: true }, { divider: true },
{ {
name: 'Export', name: 'Preferences',
iconName: 'download', iconName: 'sliders',
action: async () => console.log('Export'), action: async () => console.log('Preferences'),
},
{
name: 'Import',
iconName: 'upload',
action: async () => console.log('Import'),
}, },
]} ]}
></dees-contextmenu> ></dees-contextmenu>
</div> </dees-panel>
</div> </div>
`; `;

View File

@ -31,7 +31,7 @@ export class DeesContextmenu extends DeesElement {
// STATIC // STATIC
// This will store all the accumulated menu items // This will store all the accumulated menu items
public static contextMenuDeactivated = false; public static contextMenuDeactivated = false;
public static accumulatedMenuItems: (plugins.tsclass.website.IMenuItem & { shortcut?: string; disabled?: boolean } | { divider: true })[] = []; public static accumulatedMenuItems: (plugins.tsclass.website.IMenuItem & { shortcut?: string; disabled?: boolean; submenu?: (plugins.tsclass.website.IMenuItem & { shortcut?: string; disabled?: boolean } | { divider: true })[] } | { divider: true })[] = [];
// Add a global event listener for the right-click context menu // Add a global event listener for the right-click context menu
public static initializeGlobalListener() { public static initializeGlobalListener() {
@ -41,16 +41,16 @@ export class DeesContextmenu extends DeesElement {
} }
event.preventDefault(); event.preventDefault();
// Get the target element of the right-click
let target: EventTarget | null = event.target;
// Clear previously accumulated items // Clear previously accumulated items
DeesContextmenu.accumulatedMenuItems = []; DeesContextmenu.accumulatedMenuItems = [];
// Traverse up the DOM tree to accumulate menu items // Use composedPath to properly traverse shadow DOM boundaries
while (target) { const path = event.composedPath();
if ((target as any).getContextMenuItems) {
const items = (target as any).getContextMenuItems(); // Traverse the composed path to accumulate menu items
for (const element of path) {
if ((element as any).getContextMenuItems) {
const items = (element as any).getContextMenuItems();
if (items && items.length > 0) { if (items && items.length > 0) {
if (DeesContextmenu.accumulatedMenuItems.length > 0) { if (DeesContextmenu.accumulatedMenuItems.length > 0) {
DeesContextmenu.accumulatedMenuItems.push({ divider: true }); DeesContextmenu.accumulatedMenuItems.push({ divider: true });
@ -58,7 +58,6 @@ export class DeesContextmenu extends DeesElement {
DeesContextmenu.accumulatedMenuItems.push(...items); DeesContextmenu.accumulatedMenuItems.push(...items);
} }
} }
target = (target as Node).parentNode;
} }
// Open the context menu with the accumulated items // Open the context menu with the accumulated items
@ -67,7 +66,7 @@ export class DeesContextmenu extends DeesElement {
} }
// allows opening of a contextmenu with options // allows opening of a contextmenu with options
public static async openContextMenuWithOptions(eventArg: MouseEvent, menuItemsArg: (plugins.tsclass.website.IMenuItem & { shortcut?: string; disabled?: boolean } | { divider: true })[]) { public static async openContextMenuWithOptions(eventArg: MouseEvent, menuItemsArg: (plugins.tsclass.website.IMenuItem & { shortcut?: string; disabled?: boolean; submenu?: (plugins.tsclass.website.IMenuItem & { shortcut?: string; disabled?: boolean } | { divider: true })[] } | { divider: true })[]) {
if (this.contextMenuDeactivated) { if (this.contextMenuDeactivated) {
return; return;
} }
@ -80,8 +79,13 @@ export class DeesContextmenu extends DeesElement {
contextMenu.style.transform = 'scale(0.95) translateY(-10px)'; contextMenu.style.transform = 'scale(0.95) translateY(-10px)';
contextMenu.menuItems = menuItemsArg; contextMenu.menuItems = menuItemsArg;
contextMenu.windowLayer = await DeesWindowLayer.createAndShow(); contextMenu.windowLayer = await DeesWindowLayer.createAndShow();
contextMenu.windowLayer.addEventListener('click', async () => { contextMenu.windowLayer.addEventListener('click', async (event) => {
// Check if click is on the context menu or its submenus
const clickedElement = event.target as HTMLElement;
const isContextMenu = clickedElement.closest('dees-contextmenu');
if (!isContextMenu) {
await contextMenu.destroy(); await contextMenu.destroy();
}
}) })
document.body.append(contextMenu); document.body.append(contextMenu);
@ -123,9 +127,13 @@ export class DeesContextmenu extends DeesElement {
@property({ @property({
type: Array, type: Array,
}) })
public menuItems: (plugins.tsclass.website.IMenuItem & { shortcut?: string; disabled?: boolean; divider?: never } | { divider: true })[] = []; public menuItems: (plugins.tsclass.website.IMenuItem & { shortcut?: string; disabled?: boolean; submenu?: (plugins.tsclass.website.IMenuItem & { shortcut?: string; disabled?: boolean } | { divider: true })[]; divider?: never } | { divider: true })[] = [];
windowLayer: DeesWindowLayer; windowLayer: DeesWindowLayer;
private submenu: DeesContextmenu | null = null;
private submenuTimeout: any = null;
private parentMenu: DeesContextmenu | null = null;
constructor() { constructor() {
super(); super();
this.tabIndex = 0; this.tabIndex = 0;
@ -167,13 +175,22 @@ export class DeesContextmenu extends DeesElement {
cursor: default; cursor: default;
transition: background 0.1s; transition: background 0.1s;
line-height: 1; line-height: 1;
position: relative;
} }
.menuitem:hover { .menuitem:hover {
background: ${cssManager.bdTheme('rgba(0, 0, 0, 0.04)', 'rgba(255, 255, 255, 0.08)')}; background: ${cssManager.bdTheme('rgba(0, 0, 0, 0.04)', 'rgba(255, 255, 255, 0.08)')};
} }
.menuitem:active { .menuitem.has-submenu::after {
content: '';
position: absolute;
right: 8px;
font-size: 16px;
opacity: 0.5;
}
.menuitem:active:not(.has-submenu) {
background: ${cssManager.bdTheme('rgba(0, 0, 0, 0.08)', 'rgba(255, 255, 255, 0.12)')}; background: ${cssManager.bdTheme('rgba(0, 0, 0, 0.08)', 'rgba(255, 255, 255, 0.12)')};
} }
@ -215,14 +232,20 @@ export class DeesContextmenu extends DeesElement {
return html`<div class="menu-divider"></div>`; return html`<div class="menu-divider"></div>`;
} }
const menuItem = menuItemArg as plugins.tsclass.website.IMenuItem & { shortcut?: string; disabled?: boolean }; const menuItem = menuItemArg as plugins.tsclass.website.IMenuItem & { shortcut?: string; disabled?: boolean; submenu?: any };
const hasSubmenu = menuItem.submenu && menuItem.submenu.length > 0;
return html` return html`
<div class="menuitem ${menuItem.disabled ? 'disabled' : ''}" @click=${() => !menuItem.disabled && this.handleClick(menuItem)}> <div
class="menuitem ${menuItem.disabled ? 'disabled' : ''} ${hasSubmenu ? 'has-submenu' : ''}"
@click=${() => !menuItem.disabled && !hasSubmenu && this.handleClick(menuItem)}
@mouseenter=${() => this.handleMenuItemHover(menuItem, hasSubmenu)}
@mouseleave=${() => this.handleMenuItemLeave()}
>
${menuItem.iconName ? html` ${menuItem.iconName ? html`
<dees-icon .icon="${`lucide:${menuItem.iconName}`}"></dees-icon> <dees-icon .icon="${`lucide:${menuItem.iconName}`}"></dees-icon>
` : ''} ` : ''}
<span class="menuitem-text">${menuItem.name}</span> <span class="menuitem-text">${menuItem.name}</span>
${menuItem.shortcut ? html` ${menuItem.shortcut && !hasSubmenu ? html`
<span class="menuitem-shortcut">${menuItem.shortcut}</span> <span class="menuitem-shortcut">${menuItem.shortcut}</span>
` : ''} ` : ''}
</div> </div>
@ -282,18 +305,152 @@ export class DeesContextmenu extends DeesElement {
public async handleClick(menuItem: plugins.tsclass.website.IMenuItem & { shortcut?: string; disabled?: boolean }) { public async handleClick(menuItem: plugins.tsclass.website.IMenuItem & { shortcut?: string; disabled?: boolean }) {
menuItem.action(); menuItem.action();
await this.destroy();
// Close all menus in the chain (this menu and all parent menus)
await this.destroyAll();
}
private async handleMenuItemHover(menuItem: plugins.tsclass.website.IMenuItem & { submenu?: any }, hasSubmenu: boolean) {
// Clear any existing timeout
if (this.submenuTimeout) {
clearTimeout(this.submenuTimeout);
this.submenuTimeout = null;
}
// Hide any existing submenu if hovering a different item
if (this.submenu) {
await this.hideSubmenu();
}
// Show submenu if this item has one
if (hasSubmenu && menuItem.submenu) {
this.submenuTimeout = setTimeout(() => {
this.showSubmenu(menuItem);
}, 200); // Small delay to prevent accidental triggers
}
}
private handleMenuItemLeave() {
// Add a delay before hiding to allow moving to submenu
if (this.submenuTimeout) {
clearTimeout(this.submenuTimeout);
}
this.submenuTimeout = setTimeout(() => {
if (this.submenu && !this.submenu.matches(':hover')) {
this.hideSubmenu();
}
}, 300);
}
private async showSubmenu(menuItem: plugins.tsclass.website.IMenuItem & { submenu?: any }) {
if (!menuItem.submenu || menuItem.submenu.length === 0) return;
// Find the menu item element
const menuItems = Array.from(this.shadowRoot.querySelectorAll('.menuitem'));
const menuItemElement = menuItems.find(el => el.querySelector('.menuitem-text')?.textContent === menuItem.name) as HTMLElement;
if (!menuItemElement) return;
// Create submenu
this.submenu = new DeesContextmenu();
this.submenu.menuItems = menuItem.submenu;
this.submenu.parentMenu = this;
this.submenu.style.position = 'fixed';
this.submenu.style.zIndex = String(parseInt(this.style.zIndex) + 1);
this.submenu.style.opacity = '0';
this.submenu.style.transform = 'scale(0.95)';
// Don't create a window layer for submenus
document.body.append(this.submenu);
// Position submenu
await domtools.plugins.smartdelay.delayFor(0);
const itemRect = menuItemElement.getBoundingClientRect();
const menuRect = this.getBoundingClientRect();
const submenuRect = this.submenu.getBoundingClientRect();
const windowWidth = window.innerWidth;
let left = menuRect.right - 4; // Slight overlap
let top = itemRect.top;
// Check if submenu would go off right edge
if (left + submenuRect.width > windowWidth - 10) {
// Show on left side instead
left = menuRect.left - submenuRect.width + 4;
}
// Adjust vertical position if needed
if (top + submenuRect.height > window.innerHeight - 10) {
top = window.innerHeight - submenuRect.height - 10;
}
this.submenu.style.left = `${left}px`;
this.submenu.style.top = `${top}px`;
// Animate in
await domtools.plugins.smartdelay.delayFor(0);
this.submenu.style.opacity = '1';
this.submenu.style.transform = 'scale(1)';
// Handle submenu hover
this.submenu.addEventListener('mouseenter', () => {
if (this.submenuTimeout) {
clearTimeout(this.submenuTimeout);
this.submenuTimeout = null;
}
});
this.submenu.addEventListener('mouseleave', () => {
this.handleMenuItemLeave();
});
}
private async hideSubmenu() {
if (!this.submenu) return;
await this.submenu.destroy();
this.submenu = null;
} }
public async destroy() { public async destroy() {
if (this.windowLayer) { // Clear timeout
if (this.submenuTimeout) {
clearTimeout(this.submenuTimeout);
this.submenuTimeout = null;
}
// Destroy submenu first
if (this.submenu) {
await this.submenu.destroy();
this.submenu = null;
}
// Only destroy window layer if this is not a submenu
if (this.windowLayer && !this.parentMenu) {
this.windowLayer.destroy(); this.windowLayer.destroy();
} }
this.style.opacity = '0'; this.style.opacity = '0';
this.style.transform = 'scale(0.95) translateY(-10px)'; this.style.transform = 'scale(0.95) translateY(-10px)';
await domtools.plugins.smartdelay.delayFor(100); await domtools.plugins.smartdelay.delayFor(100);
if (this.parentElement) {
this.parentElement.removeChild(this); this.parentElement.removeChild(this);
} }
}
/**
* Destroys this menu and all parent menus in the chain
*/
public async destroyAll() {
// First destroy parent menus if they exist
if (this.parentMenu) {
await this.parentMenu.destroyAll();
} else {
// If we're at the top level, just destroy this menu
await this.destroy();
}
}
} }
DeesContextmenu.initializeGlobalListener(); DeesContextmenu.initializeGlobalListener();

View File

@ -1,18 +1,199 @@
import { html } from '@design.estate/dees-element'; import { html, cssManager } from '@design.estate/dees-element';
export const demoFunc = () => html` <style> export const demoFunc = () => html`
.demoWrapper { <style>
.demoWrapper {
box-sizing: border-box; box-sizing: border-box;
position: absolute; position: relative;
width: 100%; width: 100%;
height: 100%; min-height: 100vh;
padding: 20px; padding: 48px;
background: none; background: ${cssManager.bdTheme('#f8f9fa', '#0a0a0a')};
display: flex;
flex-direction: column;
gap: 32px;
}
.section {
max-width: 900px;
width: 100%;
margin: 0 auto;
}
.section-title {
font-size: 18px;
font-weight: 600;
margin-bottom: 16px;
color: ${cssManager.bdTheme('#09090b', '#fafafa')};
}
.section-description {
font-size: 14px;
color: ${cssManager.bdTheme('#71717a', '#a1a1aa')};
margin-bottom: 16px;
}
</style>
<div class="demoWrapper">
<div class="section">
<div class="section-title">TypeScript Code Example</div>
<div class="section-description">A comprehensive TypeScript code example with various syntax highlighting.</div>
<dees-dataview-codebox proglang="typescript">
interface User {
id: number;
name: string;
email: string;
isActive: boolean;
} }
</style>
<div class="demoWrapper"> class UserService {
<dees-dataview-codebox proglang="typescript"> private users: User[] = [];
import * as text from './hello'; const hiThere = 'nice'; const myFunction = async () => {
console.log('nice one'); } constructor(private apiUrl: string) {
</dees-dataview-codebox> console.log('UserService initialized');
</div>` }
async getUsers(): Promise<User[]> {
try {
const response = await fetch(this.apiUrl);
const data = await response.json();
return data.users;
} catch (error) {
console.error('Failed to fetch users:', error);
return [];
}
}
addUser(user: User): void {
this.users.push(user);
}
}
// Usage example
const service = new UserService('https://api.example.com/users');
const users = await service.getUsers();
console.log('Found users:', users.length);
</dees-dataview-codebox>
</div>
<div class="section">
<div class="section-title">JavaScript Example</div>
<div class="section-description">Modern JavaScript with ES6+ features.</div>
<dees-dataview-codebox proglang="javascript">
// Array manipulation examples
const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map(n => n * 2);
const filtered = numbers.filter(n => n > 3);
// Object destructuring
const user = { name: 'John', age: 30, city: 'New York' };
const { name, age } = user;
// Promise handling
const fetchData = async (url) => {
const response = await fetch(url);
return response.json();
};
// Modern syntax
const greet = (name = 'World') => \`Hello, \${name}!\`;
console.log(greet('ShadCN'));
</dees-dataview-codebox>
</div>
<div class="section">
<div class="section-title">Python Example</div>
<div class="section-description">Python code with classes and type hints.</div>
<dees-dataview-codebox proglang="python">
from typing import List, Optional
import asyncio
class DataProcessor:
"""A simple data processor class"""
def __init__(self, name: str):
self.name = name
self.data: List[dict] = []
async def process_data(self, items: List[dict]) -> List[dict]:
"""Process data items asynchronously"""
results = []
for item in items:
# Simulate async processing
await asyncio.sleep(0.1)
results.append({
'id': item.get('id'),
'processed': True,
'processor': self.name
})
return results
def get_summary(self) -> dict:
return {
'processor': self.name,
'items_processed': len(self.data)
}
# Usage
processor = DataProcessor("Main")
data = await processor.process_data([{'id': 1}, {'id': 2}])
</dees-dataview-codebox>
</div>
<div class="section">
<div class="section-title">CSS Example</div>
<div class="section-description">Modern CSS with custom properties and animations. Note the shorter language label.</div>
<dees-dataview-codebox proglang="css">
/* Modern CSS with custom properties */
:root {
--primary-color: #3b82f6;
--secondary-color: #10b981;
--background: #ffffff;
--text-color: #09090b;
--border-radius: 6px;
}
.card {
background: var(--background);
border: 1px solid #e5e7eb;
border-radius: var(--border-radius);
padding: 24px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
transition: all 0.2s ease;
}
.card:hover {
transform: translateY(-2px);
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
</dees-dataview-codebox>
</div>
<div class="section">
<div class="section-title">JSON Example</div>
<div class="section-description">JSON configuration with proper formatting.</div>
<dees-dataview-codebox proglang="json">
{
"name": "@design.estate/dees-catalog",
"version": "1.10.7",
"description": "A comprehensive catalog of web components",
"main": "dist_ts_web/index.js",
"type": "module",
"scripts": {
"build": "tsbuild tsfolders --allowimplicitany && tsbundle element --production",
"watch": "tswatch element",
"test": "tstest test/ --web --verbose"
},
"dependencies": {
"@design.estate/dees-element": "^2.0.45",
"highlight.js": "^11.9.0"
}
}
</dees-dataview-codebox>
</div>
</div>
`

View File

@ -8,6 +8,7 @@ import {
state, state,
cssManager, cssManager,
} from '@design.estate/dees-element'; } from '@design.estate/dees-element';
import { cssGeistFontFamily, cssMonoFontFamily } from './00fonts.js';
import hlight from 'highlight.js'; import hlight from 'highlight.js';
@ -48,27 +49,27 @@ export class DeesDataviewCodebox extends DeesElement {
display: block; display: block;
text-align: left; text-align: left;
font-size: 16px; font-size: 16px;
font-family: 'Geist Sans', sans-serif; font-family: ${cssGeistFontFamily};
} }
.mainbox { .mainbox {
position: relative; position: relative;
color: ${this.goBright ? '#333333' : '#ffffff'}; color: ${cssManager.bdTheme('#09090b', '#fafafa')};
border-top: 1px solid ${this.goBright ? '#ffffff' : '#333333'}; border: 1px solid ${cssManager.bdTheme('#e5e7eb', '#27272a')};
box-shadow: 0px 0px 5px ${this.goBright ? 'rgba(0,0,0,0.1)' : 'rgba(0,0,0,0.5)'}; box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1), 0 1px 2px rgba(0, 0, 0, 0.06);
background: ${this.goBright ? '#ffffff' : '#191919'}; background: ${cssManager.bdTheme('#ffffff', '#09090b')};
border-radius: 16px; border-radius: 6px;
overflow: hidden; overflow: hidden;
} }
.appbar { .appbar {
position: relative; position: relative;
color: ${cssManager.bdTheme('#333', '#ccc')}; color: ${cssManager.bdTheme('#71717a', '#a1a1aa')};
background: ${cssManager.bdTheme('#ffffff', '#161616')}; background: ${cssManager.bdTheme('#f9fafb', '#18181b')};
border-bottom: 1px solid ${cssManager.bdTheme('#eeeeeb', '#222222')}; border-bottom: 1px solid ${cssManager.bdTheme('#e5e7eb', '#27272a')};
height: 24px; height: 32px;
display: flex; display: flex;
font-size: 12px; font-size: 13px;
line-height: 24px; line-height: 32px;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
} }
@ -81,31 +82,38 @@ export class DeesDataviewCodebox extends DeesElement {
} }
.bottomBar { .bottomBar {
color: ${cssManager.bdTheme('#333', '#ccc')}; position: relative;
background: ${cssManager.bdTheme('#ffffff', '#161616')}; color: ${cssManager.bdTheme('#71717a', '#a1a1aa')};
border-top: 1px solid ${cssManager.bdTheme('#eeeeeb', '#222222')}; background: ${cssManager.bdTheme('#f9fafb', '#18181b')};
height: 24px; border-top: 1px solid ${cssManager.bdTheme('#e5e7eb', '#27272a')};
height: 28px;
font-size: 12px; font-size: 12px;
line-height: 24px; line-height: 28px;
text-align: right; display: flex;
padding-right: 100px; justify-content: flex-end;
align-items: stretch;
overflow: hidden;
}
.spacesLabel {
padding: 0 16px;
display: flex;
align-items: center;
} }
.languageLabel { .languageLabel {
color: ${cssManager.bdTheme('#333', '#ccc')}; color: ${cssManager.bdTheme('#3b82f6', '#3b82f6')};
font-size: 12px; font-size: 12px;
line-height: 24px; line-height: 28px;
z-index: 10; background: ${cssManager.bdTheme('rgba(59, 130, 246, 0.1)', 'rgba(59, 130, 246, 0.1)')};
background: #6596ff20; padding: 0px 16px;
display: inline-block; font-weight: 500;
position: absolute; display: flex;
bottom: 0px; align-items: center;
right: 0px;
padding: 0px 16px 0px 8px;
} }
.hljs-keyword { .hljs-keyword {
color: #ff65ec; color: ${cssManager.bdTheme('#dc2626', '#f87171')};
} }
.codegrid { .codegrid {
@ -115,10 +123,10 @@ export class DeesDataviewCodebox extends DeesElement {
} }
.lineNumbers { .lineNumbers {
color: ${this.goBright ? '#acacac' : '#666666'}; color: ${cssManager.bdTheme('#71717a', '#52525b')};
padding: 30px 16px 0px 0px; padding: 24px 16px 0px 0px;
text-align: right; text-align: right;
border-right: 1px solid ${this.goBright ? '#eaeaea' : '#222222'}; border-right: 1px solid ${cssManager.bdTheme('#e5e7eb', '#27272a')};
} }
.lineCounter:last-child { .lineCounter:last-child {
@ -128,11 +136,11 @@ export class DeesDataviewCodebox extends DeesElement {
pre { pre {
overflow-x: auto; overflow-x: auto;
margin: 0px; margin: 0px;
padding: 30px 40px; padding: 24px 24px;
} }
code { code {
font-weight: ${this.goBright ? '400' : '300'}; font-weight: 400;
padding: 0px; padding: 0px;
margin: 0px; margin: 0px;
} }
@ -142,27 +150,43 @@ export class DeesDataviewCodebox extends DeesElement {
.lineNumbers { .lineNumbers {
line-height: 1.4em; line-height: 1.4em;
font-weight: 200; font-weight: 200;
font-family: 'Intel One Mono', 'Geist Mono', 'monospace'; font-family: ${cssMonoFontFamily};
} }
.hljs-string { .hljs-string {
color: #ffa465; color: ${cssManager.bdTheme('#059669', '#10b981')};
} }
.hljs-built_in { .hljs-built_in {
color: #65ff6a; color: ${cssManager.bdTheme('#8b5cf6', '#a78bfa')};
} }
.hljs-function { .hljs-function {
color: ${this.goBright ? '#2765DF' : '#6596ff'}; color: ${cssManager.bdTheme('#3b82f6', '#60a5fa')};
} }
.hljs-params { .hljs-params {
color: ${this.goBright ? '#3DB420' : '#65d5ff'}; color: ${cssManager.bdTheme('#0891b2', '#06b6d4')};
} }
.hljs-comment { .hljs-comment {
color: ${this.goBright ? '#EF9300' : '#ffd765'}; color: ${cssManager.bdTheme('#71717a', '#71717a')};
}
.hljs-number {
color: ${cssManager.bdTheme('#ea580c', '#fb923c')};
}
.hljs-literal {
color: ${cssManager.bdTheme('#dc2626', '#f87171')};
}
.hljs-attr {
color: ${cssManager.bdTheme('#8b5cf6', '#a78bfa')};
}
.hljs-variable {
color: ${cssManager.bdTheme('#09090b', '#fafafa')};
} }
</style> </style>
<div <div
@ -197,7 +221,7 @@ export class DeesDataviewCodebox extends DeesElement {
<pre><code></code></pre> <pre><code></code></pre>
</div> </div>
<div class="bottomBar"> <div class="bottomBar">
Spaces: 2 <div class="spacesLabel">Spaces: 2</div>
<div class="languageLabel">${this.progLang}</div> <div class="languageLabel">${this.progLang}</div>
</div> </div>
</div> </div>

View File

@ -10,6 +10,7 @@ import {
} from '@design.estate/dees-element'; } from '@design.estate/dees-element';
import { demoFunc } from './dees-heading.demo.js'; import { demoFunc } from './dees-heading.demo.js';
import { cssCalSansFontFamily } from './00fonts.js';
declare global { declare global {
interface HTMLElementTagNameMap { interface HTMLElementTagNameMap {
@ -39,7 +40,7 @@ export class DeesHeading extends DeesElement {
font-weight: 600; font-weight: 600;
color: ${cssManager.bdTheme('#000', '#fff')}; color: ${cssManager.bdTheme('#000', '#fff')};
} }
h1 { font-size: 32px; font-family: 'Cal Sans'; letter-spacing: 0.025em;} h1 { font-size: 32px; font-family: ${cssCalSansFontFamily}; letter-spacing: 0.025em;}
h2 { font-size: 28px; } h2 { font-size: 28px; }
h3 { font-size: 24px; } h3 { font-size: 24px; }
h4 { font-size: 20px; } h4 { font-size: 20px; }

View File

@ -8,6 +8,7 @@ import {
} from '@design.estate/dees-element'; } from '@design.estate/dees-element';
import { DeesInputBase } from './dees-input-base.js'; import { DeesInputBase } from './dees-input-base.js';
import { demoFunc } from './dees-input-checkbox.demo.js'; import { demoFunc } from './dees-input-checkbox.demo.js';
import { cssGeistFontFamily } from './00fonts.js';
declare global { declare global {
interface HTMLElementTagNameMap { interface HTMLElementTagNameMap {
@ -44,7 +45,7 @@ export class DeesInputCheckbox extends DeesInputBase<DeesInputCheckbox> {
:host { :host {
position: relative; position: relative;
cursor: default; cursor: default;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif; font-family: ${cssGeistFontFamily};
} }
.maincontainer { .maincontainer {

View File

@ -10,6 +10,7 @@ import {
import * as domtools from '@design.estate/dees-domtools'; import * as domtools from '@design.estate/dees-domtools';
import { demoFunc } from './dees-input-dropdown.demo.js'; import { demoFunc } from './dees-input-dropdown.demo.js';
import { DeesInputBase } from './dees-input-base.js'; import { DeesInputBase } from './dees-input-base.js';
import { cssGeistFontFamily } from './00fonts.js';
declare global { declare global {
interface HTMLElementTagNameMap { interface HTMLElementTagNameMap {
@ -67,7 +68,7 @@ export class DeesInputDropdown extends DeesInputBase<DeesInputDropdown> {
} }
:host { :host {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif; font-family: ${cssGeistFontFamily};
position: relative; position: relative;
color: ${cssManager.bdTheme('hsl(0 0% 15%)', 'hsl(0 0% 90%)')}; color: ${cssManager.bdTheme('hsl(0 0% 15%)', 'hsl(0 0% 90%)')};
} }

View File

@ -1,4 +1,4 @@
import { html, css } from '@design.estate/dees-element'; import { html, css, cssManager } from '@design.estate/dees-element';
export const demoFunc = () => html` export const demoFunc = () => html`
<dees-demowrapper> <dees-demowrapper>
@ -7,10 +7,31 @@ export const demoFunc = () => html`
.demo-container { .demo-container {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 24px; gap: 32px;
padding: 48px;
background: ${cssManager.bdTheme('#f8f9fa', '#0a0a0a')};
min-height: 100vh;
}
.section {
background: ${cssManager.bdTheme('#ffffff', '#18181b')};
border: 1px solid ${cssManager.bdTheme('#e5e7eb', '#27272a')};
border-radius: 8px;
padding: 24px; padding: 24px;
max-width: 1200px; box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
margin: 0 auto; }
.section-title {
font-size: 18px;
font-weight: 600;
margin-bottom: 8px;
color: ${cssManager.bdTheme('#09090b', '#fafafa')};
}
.section-description {
font-size: 14px;
color: ${cssManager.bdTheme('#71717a', '#a1a1aa')};
margin-bottom: 24px;
} }
.settings-grid { .settings-grid {
@ -28,7 +49,10 @@ export const demoFunc = () => html`
</style> </style>
<div class="demo-container"> <div class="demo-container">
<dees-panel .title=${'Multi-Option Toggle'} .subtitle=${'Select from multiple options with a sliding indicator'}> <div class="section">
<div class="section-title">Multi-Option Toggle</div>
<div class="section-description">Select from multiple options with a smooth sliding indicator animation.</div>
<dees-input-multitoggle <dees-input-multitoggle
.label=${'Display Mode'} .label=${'Display Mode'}
.description=${'Choose how content is displayed'} .description=${'Choose how content is displayed'}
@ -36,15 +60,20 @@ export const demoFunc = () => html`
.selectedOption=${'Grid View'} .selectedOption=${'Grid View'}
></dees-input-multitoggle> ></dees-input-multitoggle>
<br><br>
<dees-input-multitoggle <dees-input-multitoggle
.label=${'T-Shirt Size'} .label=${'T-Shirt Size'}
.description=${'Select your preferred size'} .description=${'Select your preferred size'}
.options=${['XS', 'S', 'M', 'L', 'XL', 'XXL']} .options=${['XS', 'S', 'M', 'L', 'XL', 'XXL']}
.selectedOption=${'M'} .selectedOption=${'M'}
></dees-input-multitoggle> ></dees-input-multitoggle>
</dees-panel> </div>
<div class="section">
<div class="section-title">Boolean Toggle</div>
<div class="section-description">Simple on/off switches with customizable labels for clearer context.</div>
<dees-panel .title=${'Boolean Toggle'} .subtitle=${'Simple on/off switches with custom labels'}>
<dees-input-multitoggle <dees-input-multitoggle
.label=${'Notifications'} .label=${'Notifications'}
.description=${'Enable or disable push notifications'} .description=${'Enable or disable push notifications'}
@ -52,6 +81,8 @@ export const demoFunc = () => html`
.selectedOption=${'true'} .selectedOption=${'true'}
></dees-input-multitoggle> ></dees-input-multitoggle>
<br><br>
<dees-input-multitoggle <dees-input-multitoggle
.label=${'Theme Mode'} .label=${'Theme Mode'}
.description=${'Switch between light and dark theme'} .description=${'Switch between light and dark theme'}
@ -60,13 +91,15 @@ export const demoFunc = () => html`
.booleanFalseName=${'Light'} .booleanFalseName=${'Light'}
.selectedOption=${'Dark'} .selectedOption=${'Dark'}
></dees-input-multitoggle> ></dees-input-multitoggle>
</dees-panel> </div>
<div class="section">
<div class="section-title">Settings Grid</div>
<div class="section-description">Configuration options arranged in a responsive grid layout.</div>
<dees-panel .title=${'Settings Panel'} .subtitle=${'Configuration options in a horizontal layout'}>
<div class="settings-grid"> <div class="settings-grid">
<dees-input-multitoggle <dees-input-multitoggle
.label=${'Auto-Save'} .label=${'Auto-Save'}
.layoutMode=${'horizontal'}
.type=${'boolean'} .type=${'boolean'}
.booleanTrueName=${'Enabled'} .booleanTrueName=${'Enabled'}
.booleanFalseName=${'Disabled'} .booleanFalseName=${'Disabled'}
@ -75,30 +108,30 @@ export const demoFunc = () => html`
<dees-input-multitoggle <dees-input-multitoggle
.label=${'Language'} .label=${'Language'}
.layoutMode=${'horizontal'}
.options=${['English', 'German', 'French', 'Spanish']} .options=${['English', 'German', 'French', 'Spanish']}
.selectedOption=${'English'} .selectedOption=${'English'}
></dees-input-multitoggle> ></dees-input-multitoggle>
<dees-input-multitoggle <dees-input-multitoggle
.label=${'Quality'} .label=${'Quality'}
.layoutMode=${'horizontal'}
.options=${['Low', 'Medium', 'High', 'Ultra']} .options=${['Low', 'Medium', 'High', 'Ultra']}
.selectedOption=${'High'} .selectedOption=${'High'}
></dees-input-multitoggle> ></dees-input-multitoggle>
<dees-input-multitoggle <dees-input-multitoggle
.label=${'Privacy'} .label=${'Privacy'}
.layoutMode=${'horizontal'}
.type=${'boolean'} .type=${'boolean'}
.booleanTrueName=${'Private'} .booleanTrueName=${'Private'}
.booleanFalseName=${'Public'} .booleanFalseName=${'Public'}
.selectedOption=${'Private'} .selectedOption=${'Private'}
></dees-input-multitoggle> ></dees-input-multitoggle>
</div> </div>
</dees-panel> </div>
<div class="section">
<div class="section-title">States & Form Integration</div>
<div class="section-description">Examples of disabled states and integration within forms.</div>
<dees-panel .title=${'States & Form Integration'} .subtitle=${'Disabled states and form usage'}>
<dees-input-multitoggle <dees-input-multitoggle
.label=${'Account Type'} .label=${'Account Type'}
.description=${'This setting is locked'} .description=${'This setting is locked'}
@ -107,6 +140,8 @@ export const demoFunc = () => html`
.disabled=${true} .disabled=${true}
></dees-input-multitoggle> ></dees-input-multitoggle>
<br><br>
<dees-form> <dees-form>
<dees-input-text .label=${'Project Name'} .required=${true}></dees-input-text> <dees-input-text .label=${'Project Name'} .required=${true}></dees-input-text>
<dees-input-multitoggle <dees-input-multitoggle
@ -122,7 +157,7 @@ export const demoFunc = () => html`
.selectedOption=${'MIT'} .selectedOption=${'MIT'}
></dees-input-multitoggle> ></dees-input-multitoggle>
</dees-form> </dees-form>
</dees-panel> </div>
</div> </div>
</dees-demowrapper> </dees-demowrapper>
`; `;

View File

@ -57,9 +57,12 @@ export class DeesInputMultitoggle extends DeesInputBase<DeesInputMultitoggle> {
} else { } else {
this.selectedOption = val as string; this.selectedOption = val as string;
} }
this.requestUpdate();
// Defer indicator update to next frame if component not yet updated // Defer indicator update to next frame if component not yet updated
if (this.hasUpdated) { if (this.hasUpdated) {
requestAnimationFrame(() => {
this.setIndicator(); this.setIndicator();
});
} }
} }
@ -68,59 +71,71 @@ export class DeesInputMultitoggle extends DeesInputBase<DeesInputMultitoggle> {
cssManager.defaultStyles, cssManager.defaultStyles,
css` css`
:host { :host {
color: ${cssManager.bdTheme('#333', '#ccc')}; color: ${cssManager.bdTheme('#09090b', '#fafafa')};
user-select: none; user-select: none;
} }
.selections { .selections {
position: relative; position: relative;
display: flex; display: inline-flex;
flex-direction: row; align-items: center;
flex-wrap: nowrap; background: ${cssManager.bdTheme('#ffffff', '#18181b')};
background: ${cssManager.bdTheme('#fff', '#222')}; border: 1px solid ${cssManager.bdTheme('#e5e7eb', '#27272a')};
width: min-content; padding: 4px;
border-radius: 20px; border-radius: 8px;
height: 32px; box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
border-top: 1px solid ${cssManager.bdTheme('rgba(0,0,0,0.1)', 'rgba(255,255,255,0.1)')};
} }
.option { .option {
color: ${cssManager.bdTheme('#666', '#999')};
position: relative; position: relative;
padding: 0px 16px; padding: 8px 20px;
line-height: 32px; border-radius: 6px;
cursor: default; cursor: pointer;
width: min-content; /* Make the width as per the content */ white-space: nowrap;
white-space: nowrap; /* Prevent text wrapping */ transition: color 0.2s ease;
transition: all 0.1s;
font-size: 14px; font-size: 14px;
transform: translateY(-1px); font-weight: 500;
color: ${cssManager.bdTheme('#71717a', '#71717a')};
line-height: 1;
z-index: 2;
} }
.option:hover { .option:hover {
color: ${cssManager.bdTheme('#333', '#fff')}; color: ${cssManager.bdTheme('#18181b', '#e4e4e7')};
} }
.option.selected { .option.selected {
color: ${cssManager.bdTheme('#fff', '#fff')}; color: ${cssManager.bdTheme('#3b82f6', '#60a5fa')};
} }
.indicator { .indicator {
opacity: 0; opacity: 0;
position: absolute; position: absolute;
height: 24px; height: calc(100% - 8px);
left: 4px; top: 4px;
top: 3px; border-radius: 6px;
border-radius: 16px; background: ${cssManager.bdTheme('rgba(59, 130, 246, 0.15)', 'rgba(59, 130, 246, 0.15)')};
background: ${cssManager.bdTheme(colors.bright.blueActive, colors.dark.blueActive)}; transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
min-width: 24px; z-index: 1;
transition: all 0.1s ease-in-out;
} }
.indicator.no-transition { .indicator.no-transition {
transition: none; transition: none;
} }
:host([disabled]) .selections {
opacity: 0.5;
cursor: not-allowed;
}
:host([disabled]) .option {
cursor: not-allowed;
pointer-events: none;
}
:host([disabled]) .indicator {
background: ${cssManager.bdTheme('rgba(113, 113, 122, 0.15)', 'rgba(113, 113, 122, 0.15)')};
}
`, `,
]; ];
@ -148,6 +163,14 @@ export class DeesInputMultitoggle extends DeesInputBase<DeesInputMultitoggle> {
// Initialize boolean options early // Initialize boolean options early
if (this.type === 'boolean' && this.options.length === 0) { if (this.type === 'boolean' && this.options.length === 0) {
this.options = [this.booleanTrueName || 'true', this.booleanFalseName || 'false']; this.options = [this.booleanTrueName || 'true', this.booleanFalseName || 'false'];
// Set default selection for boolean if not set
if (!this.selectedOption) {
this.selectedOption = this.booleanFalseName || 'false';
}
}
// Set default selection to first option if not set
if (!this.selectedOption && this.options.length > 0) {
this.selectedOption = this.options[0];
} }
} }
@ -159,13 +182,25 @@ export class DeesInputMultitoggle extends DeesInputBase<DeesInputMultitoggle> {
} }
// Wait for the next frame to ensure DOM is fully rendered // Wait for the next frame to ensure DOM is fully rendered
await this.updateComplete; await this.updateComplete;
requestAnimationFrame(() => {
// Wait for fonts to load
if (document.fonts) {
await document.fonts.ready;
}
// Wait one more frame after fonts are loaded
await new Promise(resolve => requestAnimationFrame(resolve));
// Now set the indicator
this.setIndicator(); this.setIndicator();
});
} }
public async handleSelection(optionArg: string) { public async handleSelection(optionArg: string) {
if (this.disabled) return;
this.selectedOption = optionArg; this.selectedOption = optionArg;
this.requestUpdate();
this.changeSubject.next(this);
await this.updateComplete;
this.setIndicator(); this.setIndicator();
} }
@ -199,8 +234,8 @@ export class DeesInputMultitoggle extends DeesInputBase<DeesInputMultitoggle> {
}, 50); }, 50);
} }
indicator.style.width = `${option.clientWidth - 8}px`; indicator.style.width = `${option.clientWidth}px`;
indicator.style.left = `${option.offsetLeft + 4}px`; indicator.style.left = `${option.offsetLeft}px`;
indicator.style.opacity = '1'; indicator.style.opacity = '1';
} }
} }
@ -218,8 +253,11 @@ export class DeesInputMultitoggle extends DeesInputBase<DeesInputMultitoggle> {
} else { } else {
this.selectedOption = value as string; this.selectedOption = value as string;
} }
this.requestUpdate();
if (this.hasUpdated) { if (this.hasUpdated) {
requestAnimationFrame(() => {
this.setIndicator(); this.setIndicator();
});
} }
} }
} }

View File

@ -96,27 +96,27 @@ export class DeesInputRichtext extends DeesInputBase<string> {
margin-bottom: 8px; margin-bottom: 8px;
font-size: 14px; font-size: 14px;
font-weight: 500; font-weight: 500;
color: ${cssManager.bdTheme('#374151', '#e4e4e7')}; color: ${cssManager.bdTheme('hsl(0 0% 15%)', 'hsl(0 0% 93.9%)')};
} }
.editor-container { .editor-container {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
min-height: ${cssManager.bdTheme('200px', '200px')}; min-height: ${cssManager.bdTheme('200px', '200px')};
border: 1px solid ${cssManager.bdTheme('#e1e5e9', '#2c2c2c')}; border: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')};
border-radius: 8px; border-radius: 6px;
background: ${cssManager.bdTheme('#ffffff', '#141414')}; background: ${cssManager.bdTheme('hsl(0 0% 100%)', 'hsl(0 0% 9%)')};
overflow: hidden; overflow: hidden;
transition: all 0.2s ease; transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
} }
.editor-container:hover { .editor-container:hover {
border-color: ${cssManager.bdTheme('#d1d5db', '#404040')}; border-color: ${cssManager.bdTheme('hsl(0 0% 79.8%)', 'hsl(0 0% 20.9%)')};
} }
.editor-container.focused { .editor-container.focused {
border-color: ${cssManager.bdTheme('#0050b9', '#0069f2')}; border-color: ${cssManager.bdTheme('hsl(0 0% 9%)', 'hsl(0 0% 98%)')};
box-shadow: 0 0 0 3px ${cssManager.bdTheme('rgba(0, 80, 185, 0.1)', 'rgba(0, 105, 242, 0.1)')}; box-shadow: 0 0 0 2px ${cssManager.bdTheme('hsl(0 0% 9% / 0.05)', 'hsl(0 0% 98% / 0.05)')};
} }
.editor-toolbar { .editor-toolbar {
@ -124,8 +124,8 @@ export class DeesInputRichtext extends DeesInputBase<string> {
flex-wrap: wrap; flex-wrap: wrap;
gap: 4px; gap: 4px;
padding: 8px 12px; padding: 8px 12px;
background: ${cssManager.bdTheme('#f8f9fa', '#1a1a1a')}; background: ${cssManager.bdTheme('hsl(210 40% 96.1%)', 'hsl(0 0% 14.9%)')};
border-bottom: 1px solid ${cssManager.bdTheme('#e1e5e9', '#2c2c2c')}; border-bottom: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')};
align-items: center; align-items: center;
position: relative; position: relative;
} }
@ -142,8 +142,8 @@ export class DeesInputRichtext extends DeesInputBase<string> {
cursor: pointer; cursor: pointer;
font-size: 14px; font-size: 14px;
font-weight: 500; font-weight: 500;
color: ${cssManager.bdTheme('#374151', '#9ca3af')}; color: ${cssManager.bdTheme('hsl(215.4 16.3% 46.9%)', 'hsl(215 20.2% 65.1%)')};
transition: all 0.2s; transition: all 0.15s ease;
user-select: none; user-select: none;
} }
@ -153,13 +153,13 @@ export class DeesInputRichtext extends DeesInputBase<string> {
} }
.toolbar-button:hover { .toolbar-button:hover {
background: ${cssManager.bdTheme('#e5e7eb', '#2c2c2c')}; background: ${cssManager.bdTheme('hsl(0 0% 95.1%)', 'hsl(0 0% 14.9%)')};
color: ${cssManager.bdTheme('#1f2937', '#e4e4e7')}; color: ${cssManager.bdTheme('hsl(0 0% 9%)', 'hsl(0 0% 95%)')};
} }
.toolbar-button.active { .toolbar-button.active {
background: ${cssManager.bdTheme('#0050b9', '#0069f2')}; background: ${cssManager.bdTheme('hsl(0 0% 9%)', 'hsl(0 0% 98%)')};
color: white; color: ${cssManager.bdTheme('hsl(0 0% 100%)', 'hsl(0 0% 3.9%)')};
} }
.toolbar-button:disabled { .toolbar-button:disabled {
@ -170,7 +170,7 @@ export class DeesInputRichtext extends DeesInputBase<string> {
.toolbar-divider { .toolbar-divider {
width: 1px; width: 1px;
height: 24px; height: 24px;
background: ${cssManager.bdTheme('#d1d5db', '#404040')}; background: ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')};
margin: 0 4px; margin: 0 4px;
} }
@ -184,7 +184,7 @@ export class DeesInputRichtext extends DeesInputBase<string> {
.editor-content .ProseMirror { .editor-content .ProseMirror {
outline: none; outline: none;
line-height: 1.6; line-height: 1.6;
color: ${cssManager.bdTheme('#374151', '#e4e4e7')}; color: ${cssManager.bdTheme('hsl(0 0% 3.9%)', 'hsl(0 0% 98%)')};
min-height: 100%; min-height: 100%;
} }
@ -232,25 +232,25 @@ export class DeesInputRichtext extends DeesInputBase<string> {
} }
.editor-content .ProseMirror blockquote { .editor-content .ProseMirror blockquote {
border-left: 4px solid ${cssManager.bdTheme('#d1d5db', '#404040')}; border-left: 4px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')};
margin: 1em 0; margin: 1em 0;
padding-left: 1em; padding-left: 1em;
color: ${cssManager.bdTheme('#6b7280', '#9ca3af')}; color: ${cssManager.bdTheme('hsl(215.4 16.3% 46.9%)', 'hsl(215 20.2% 65.1%)')};
font-style: italic; font-style: italic;
} }
.editor-content .ProseMirror code { .editor-content .ProseMirror code {
background: ${cssManager.bdTheme('#f3f4f6', '#2c2c2c')}; background: ${cssManager.bdTheme('hsl(0 0% 95.1%)', 'hsl(0 0% 14.9%)')};
border-radius: 4px; border-radius: 3px;
padding: 0.2em 0.4em; padding: 0.2em 0.4em;
font-family: 'Fira Code', 'SF Mono', Monaco, 'Cascadia Code', 'Roboto Mono', Consolas, 'Courier New', monospace; font-family: 'Intel One Mono', 'Fira Code', 'SF Mono', Monaco, 'Cascadia Code', 'Roboto Mono', Consolas, 'Courier New', monospace;
font-size: 0.9em; font-size: 0.9em;
color: ${cssManager.bdTheme('#e11d48', '#f87171')}; color: ${cssManager.bdTheme('hsl(0 0% 15%)', 'hsl(0 0% 93.9%)')};
} }
.editor-content .ProseMirror pre { .editor-content .ProseMirror pre {
background: ${cssManager.bdTheme('#1f2937', '#0a0a0a')}; background: ${cssManager.bdTheme('hsl(0 0% 3.9%)', 'hsl(0 0% 98%)')};
color: ${cssManager.bdTheme('#f9fafb', '#e4e4e7')}; color: ${cssManager.bdTheme('hsl(0 0% 98%)', 'hsl(0 0% 3.9%)')};
border-radius: 6px; border-radius: 6px;
padding: 1em; padding: 1em;
margin: 1em 0; margin: 1em 0;
@ -265,21 +265,21 @@ export class DeesInputRichtext extends DeesInputBase<string> {
} }
.editor-content .ProseMirror a { .editor-content .ProseMirror a {
color: ${cssManager.bdTheme('#0050b9', '#0069f2')}; color: ${cssManager.bdTheme('hsl(222.2 47.4% 51.2%)', 'hsl(217.2 91.2% 59.8%)')};
text-decoration: underline; text-decoration: underline;
cursor: pointer; cursor: pointer;
} }
.editor-content .ProseMirror a:hover { .editor-content .ProseMirror a:hover {
color: ${cssManager.bdTheme('#0069f2', '#0084ff')}; color: ${cssManager.bdTheme('hsl(222.2 47.4% 41.2%)', 'hsl(217.2 91.2% 69.8%)')};
} }
.editor-footer { .editor-footer {
padding: 8px 12px; padding: 8px 12px;
background: ${cssManager.bdTheme('#f8f9fa', '#1a1a1a')}; background: ${cssManager.bdTheme('hsl(210 40% 96.1%)', 'hsl(0 0% 14.9%)')};
border-top: 1px solid ${cssManager.bdTheme('#e1e5e9', '#2c2c2c')}; border-top: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')};
font-size: 12px; font-size: 12px;
color: ${cssManager.bdTheme('#6b7280', '#9ca3af')}; color: ${cssManager.bdTheme('hsl(215.4 16.3% 46.9%)', 'hsl(215 20.2% 65.1%)')};
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
@ -295,8 +295,8 @@ export class DeesInputRichtext extends DeesInputBase<string> {
top: 100%; top: 100%;
left: 0; left: 0;
right: 0; right: 0;
background: ${cssManager.bdTheme('#ffffff', '#1a1a1a')}; background: ${cssManager.bdTheme('hsl(0 0% 100%)', 'hsl(0 0% 9%)')};
border: 1px solid ${cssManager.bdTheme('#d1d5db', '#404040')}; border: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')};
border-radius: 6px; border-radius: 6px;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1); box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
padding: 12px; padding: 12px;
@ -310,17 +310,18 @@ export class DeesInputRichtext extends DeesInputBase<string> {
.link-input input { .link-input input {
width: 100%; width: 100%;
padding: 8px 12px; padding: 8px 12px;
border: 1px solid ${cssManager.bdTheme('#d1d5db', '#404040')}; border: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')};
border-radius: 4px; border-radius: 6px;
outline: none; outline: none;
font-size: 14px; font-size: 14px;
background: ${cssManager.bdTheme('#ffffff', '#0a0a0a')}; background: ${cssManager.bdTheme('hsl(0 0% 100%)', 'hsl(0 0% 9%)')};
color: ${cssManager.bdTheme('#374151', '#e4e4e7')}; color: ${cssManager.bdTheme('hsl(0 0% 3.9%)', 'hsl(0 0% 98%)')};
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
} }
.link-input input:focus { .link-input input:focus {
border-color: ${cssManager.bdTheme('#0050b9', '#0069f2')}; border-color: ${cssManager.bdTheme('hsl(0 0% 9%)', 'hsl(0 0% 98%)')};
box-shadow: 0 0 0 2px ${cssManager.bdTheme('rgba(0, 80, 185, 0.1)', 'rgba(0, 105, 242, 0.1)')}; box-shadow: 0 0 0 2px ${cssManager.bdTheme('hsl(0 0% 9% / 0.05)', 'hsl(0 0% 98% / 0.05)')};
} }
.link-input-buttons { .link-input-buttons {
@ -331,33 +332,36 @@ export class DeesInputRichtext extends DeesInputBase<string> {
.link-input-buttons button { .link-input-buttons button {
padding: 6px 12px; padding: 6px 12px;
border: 1px solid ${cssManager.bdTheme('#d1d5db', '#404040')}; border: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')};
border-radius: 4px; border-radius: 4px;
background: ${cssManager.bdTheme('#ffffff', '#1a1a1a')}; background: ${cssManager.bdTheme('hsl(0 0% 100%)', 'hsl(0 0% 9%)')};
cursor: pointer; cursor: pointer;
font-size: 12px; font-size: 12px;
color: ${cssManager.bdTheme('#374151', '#e4e4e7')}; color: ${cssManager.bdTheme('hsl(0 0% 45.1%)', 'hsl(0 0% 63.9%)')};
transition: all 0.2s; transition: all 0.15s ease;
font-weight: 500;
} }
.link-input-buttons button:hover { .link-input-buttons button:hover {
background: ${cssManager.bdTheme('#f3f4f6', '#2c2c2c')}; background: ${cssManager.bdTheme('hsl(0 0% 95.1%)', 'hsl(0 0% 14.9%)')};
color: ${cssManager.bdTheme('hsl(0 0% 9%)', 'hsl(0 0% 95%)')};
} }
.link-input-buttons button.primary { .link-input-buttons button.primary {
background: ${cssManager.bdTheme('#0050b9', '#0069f2')}; background: ${cssManager.bdTheme('hsl(0 0% 9%)', 'hsl(0 0% 98%)')};
color: white; color: ${cssManager.bdTheme('hsl(0 0% 100%)', 'hsl(0 0% 3.9%)')};
border-color: ${cssManager.bdTheme('#0050b9', '#0069f2')}; border-color: ${cssManager.bdTheme('hsl(0 0% 9%)', 'hsl(0 0% 98%)')};
} }
.link-input-buttons button.primary:hover { .link-input-buttons button.primary:hover {
background: ${cssManager.bdTheme('#0069f2', '#0084ff')}; background: ${cssManager.bdTheme('hsl(0 0% 15%)', 'hsl(0 0% 93.9%)')};
border-color: ${cssManager.bdTheme('hsl(0 0% 15%)', 'hsl(0 0% 93.9%)')};
} }
.description { .description {
margin-top: 8px; margin-top: 8px;
font-size: 12px; font-size: 12px;
color: ${cssManager.bdTheme('#6b7280', '#9ca3af')}; color: ${cssManager.bdTheme('hsl(215.4 16.3% 46.9%)', 'hsl(215 20.2% 65.1%)')};
line-height: 1.4; line-height: 1.4;
} }

View File

@ -1,6 +1,7 @@
import * as colors from './00colors.js'; import * as colors from './00colors.js';
import { DeesInputBase } from './dees-input-base.js'; import { DeesInputBase } from './dees-input-base.js';
import { demoFunc } from './dees-input-text.demo.js'; import { demoFunc } from './dees-input-text.demo.js';
import { cssGeistFontFamily, cssMonoFontFamily } from './00fonts.js';
import { import {
customElement, customElement,
@ -65,7 +66,7 @@ export class DeesInputText extends DeesInputBase {
:host { :host {
position: relative; position: relative;
z-index: auto; z-index: auto;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif; font-family: ${cssGeistFontFamily};
} }
.maincontainer { .maincontainer {
@ -80,18 +81,18 @@ export class DeesInputText extends DeesInputBase {
padding: 0 12px; padding: 0 12px;
font-size: 14px; font-size: 14px;
line-height: 40px; line-height: 40px;
background: transparent; background: ${cssManager.bdTheme('hsl(0 0% 100%)', 'hsl(0 0% 9%)')};
border: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')}; border: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')};
border-radius: 6px; border-radius: 6px;
transition: all 0.15s ease; transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
outline: none; outline: none;
cursor: text; cursor: text;
font-family: inherit; font-family: inherit;
color: ${cssManager.bdTheme('hsl(0 0% 9%)', 'hsl(0 0% 95%)')}; color: ${cssManager.bdTheme('hsl(0 0% 3.9%)', 'hsl(0 0% 98%)')};
} }
input::placeholder { input::placeholder {
color: ${cssManager.bdTheme('hsl(0 0% 63.9%)', 'hsl(0 0% 45.1%)')}; color: ${cssManager.bdTheme('hsl(0 0% 45.1%)', 'hsl(0 0% 63.9%)')};
} }
input:hover:not(:disabled):not(:focus) { input:hover:not(:disabled):not(:focus) {
@ -100,14 +101,14 @@ export class DeesInputText extends DeesInputBase {
input:focus { input:focus {
outline: none; outline: none;
border-color: ${cssManager.bdTheme('hsl(222.2 47.4% 51.2%)', 'hsl(217.2 91.2% 59.8%)')}; border-color: ${cssManager.bdTheme('hsl(0 0% 9%)', 'hsl(0 0% 98%)')};
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)')}; box-shadow: 0 0 0 2px ${cssManager.bdTheme('hsl(0 0% 9% / 0.05)', 'hsl(0 0% 98% / 0.05)')};
} }
input:disabled { input:disabled {
background: ${cssManager.bdTheme('hsl(0 0% 95.1%)', 'hsl(0 0% 14.9%)')}; 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%)')}; 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%)')}; color: ${cssManager.bdTheme('hsl(0 0% 45.1%)', 'hsl(0 0% 63.9%)')};
cursor: not-allowed; cursor: not-allowed;
opacity: 0.5; opacity: 0.5;
} }
@ -166,7 +167,8 @@ export class DeesInputText extends DeesInputBase {
} }
:host([validation-state="invalid"]) input:focus { :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)')}; border-color: ${cssManager.bdTheme('hsl(0 84.2% 60.2%)', 'hsl(0 72.2% 50.6%)')};
box-shadow: 0 0 0 2px ${cssManager.bdTheme('hsl(0 84.2% 60.2% / 0.05)', 'hsl(0 72.2% 50.6% / 0.05)')};
} }
/* Warning state for input */ /* Warning state for input */
@ -175,7 +177,8 @@ export class DeesInputText extends DeesInputBase {
} }
:host([validation-state="warn"]) input:focus { :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)')}; border-color: ${cssManager.bdTheme('hsl(25 95% 53%)', 'hsl(25 95% 63%)')};
box-shadow: 0 0 0 2px ${cssManager.bdTheme('hsl(25 95% 53% / 0.05)', 'hsl(25 95% 63% / 0.05)')};
} }
/* Valid state for input */ /* Valid state for input */
@ -184,7 +187,8 @@ export class DeesInputText extends DeesInputBase {
} }
:host([validation-state="valid"]) input:focus { :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)')}; border-color: ${cssManager.bdTheme('hsl(142.1 76.2% 36.3%)', 'hsl(142.1 70.6% 45.3%)')};
box-shadow: 0 0 0 2px ${cssManager.bdTheme('hsl(142.1 76.2% 36.3% / 0.05)', 'hsl(142.1 70.6% 45.3% / 0.05)')};
} }
`, `,
]; ];
@ -193,7 +197,7 @@ export class DeesInputText extends DeesInputBase {
return html` return html`
<style> <style>
input { input {
font-family: ${this.isPasswordBool ? 'SF Mono, Monaco, Consolas, Liberation Mono, Courier New, monospace' : 'inherit'}; font-family: ${this.isPasswordBool ? cssMonoFontFamily : 'inherit'};
letter-spacing: ${this.isPasswordBool ? '0.5px' : 'normal'}; letter-spacing: ${this.isPasswordBool ? '0.5px' : 'normal'};
padding-right: ${this.isPasswordBool ? '48px' : '12px'}; padding-right: ${this.isPasswordBool ? '48px' : '12px'};
} }

View File

@ -1,6 +1,7 @@
import * as colors from './00colors.js'; import * as colors from './00colors.js';
import * as plugins from './00plugins.js'; import * as plugins from './00plugins.js';
import { zIndexLayers, zIndexRegistry } from './00zindex.js'; import { zIndexLayers, zIndexRegistry } from './00zindex.js';
import { cssGeistFontFamily } from './00fonts.js';
import { demoFunc } from './dees-modal.demo.js'; import { demoFunc } from './dees-modal.demo.js';
import { import {
@ -117,7 +118,7 @@ export class DeesModal extends DeesElement {
cssManager.defaultStyles, cssManager.defaultStyles,
css` css`
:host { :host {
font-family: 'Geist Sans', sans-serif; font-family: ${cssGeistFontFamily};
color: ${cssManager.bdTheme('#333', '#fff')}; color: ${cssManager.bdTheme('#333', '#fff')};
will-change: transform; will-change: transform;
} }
@ -138,12 +139,12 @@ export class DeesModal extends DeesElement {
opacity: 0; opacity: 0;
min-height: 120px; min-height: 120px;
max-height: calc(100vh - 40px); max-height: calc(100vh - 40px);
background: ${cssManager.bdTheme('#ffffff', '#111')}; background: ${cssManager.bdTheme('#ffffff', '#09090b')};
border-radius: 8px; border-radius: 6px;
border: 1px solid ${cssManager.bdTheme('#e0e0e0', '#333')}; border: 1px solid ${cssManager.bdTheme('#e5e7eb', '#27272a')};
transition: all 0.2s; transition: all 0.2s ease;
overflow: hidden; overflow: hidden;
box-shadow: ${cssManager.bdTheme('0px 2px 10px rgba(0, 0, 0, 0.1)', '0px 2px 5px rgba(0, 0, 0, 0.5)')}; box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1), 0 1px 2px rgba(0, 0, 0, 0.06);
margin: 20px; margin: 20px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@ -192,6 +193,7 @@ export class DeesModal extends DeesElement {
max-height: 100vh !important; max-height: 100vh !important;
margin: 0; margin: 0;
border-radius: 0; border-radius: 0;
border: none;
} }
} }
@ -208,12 +210,12 @@ export class DeesModal extends DeesElement {
.modal .heading { .modal .heading {
height: 40px; height: 40px;
min-height: 40px; min-height: 40px;
font-family: 'Geist Sans', sans-serif; font-family: ${cssGeistFontFamily};
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
padding: 0 12px; padding: 0 12px;
border-bottom: 1px solid ${cssManager.bdTheme('#e0e0e0', '#333')}; border-bottom: 1px solid ${cssManager.bdTheme('#e5e7eb', '#27272a')};
position: relative; position: relative;
flex-shrink: 0; flex-shrink: 0;
} }
@ -231,23 +233,23 @@ export class DeesModal extends DeesElement {
.modal .heading .header-button { .modal .heading .header-button {
width: 28px; width: 28px;
height: 28px; height: 28px;
border-radius: 6px; border-radius: 4px;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
cursor: pointer; cursor: pointer;
transition: all 0.2s; transition: all 0.15s ease;
background: transparent; background: transparent;
color: ${cssManager.bdTheme('#666', '#999')}; color: ${cssManager.bdTheme('#71717a', '#a1a1aa')};
} }
.modal .heading .header-button:hover { .modal .heading .header-button:hover {
background: ${cssManager.bdTheme('rgba(0, 0, 0, 0.08)', 'rgba(255, 255, 255, 0.08)')}; background: ${cssManager.bdTheme('#f4f4f5', '#27272a')};
color: ${cssManager.bdTheme('#333', '#fff')}; color: ${cssManager.bdTheme('#09090b', '#fafafa')};
} }
.modal .heading .header-button:active { .modal .heading .header-button:active {
background: ${cssManager.bdTheme('rgba(0, 0, 0, 0.12)', 'rgba(255, 255, 255, 0.12)')}; background: ${cssManager.bdTheme('#e5e7eb', '#3f3f46')};
} }
.modal .heading .header-button dees-icon { .modal .heading .header-button dees-icon {
@ -263,6 +265,7 @@ export class DeesModal extends DeesElement {
font-size: 14px; font-size: 14px;
line-height: 40px; line-height: 40px;
padding: 0 40px; padding: 0 40px;
color: ${cssManager.bdTheme('#09090b', '#fafafa')};
} }
.modal .content { .modal .content {
@ -275,7 +278,7 @@ export class DeesModal extends DeesElement {
.modal .bottomButtons { .modal .bottomButtons {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
border-top: 1px solid ${cssManager.bdTheme('#e0e0e0', '#333')}; border-top: 1px solid ${cssManager.bdTheme('#e5e7eb', '#27272a')};
justify-content: flex-end; justify-content: flex-end;
gap: 8px; gap: 8px;
padding: 8px; padding: 8px;
@ -284,39 +287,43 @@ export class DeesModal extends DeesElement {
.modal .bottomButtons .bottomButton { .modal .bottomButtons .bottomButton {
padding: 8px 16px; padding: 8px 16px;
border-radius: 6px; border-radius: 4px;
line-height: 16px; line-height: 16px;
text-align: center; text-align: center;
font-size: 14px; font-size: 14px;
font-weight: 500; font-weight: 500;
cursor: pointer; cursor: pointer;
user-select: none; user-select: none;
transition: all 0.2s; transition: all 0.15s ease;
background: ${cssManager.bdTheme('rgba(0, 0, 0, 0.05)', 'rgba(255, 255, 255, 0.05)')}; background: ${cssManager.bdTheme('#ffffff', '#27272a')};
border: 1px solid ${cssManager.bdTheme('#e5e7eb', '#3f3f46')};
color: ${cssManager.bdTheme('#09090b', '#fafafa')};
white-space: nowrap; white-space: nowrap;
} }
.modal .bottomButtons .bottomButton:hover { .modal .bottomButtons .bottomButton:hover {
background: ${cssManager.bdTheme(colors.bright.blue, colors.dark.blue)}; background: ${cssManager.bdTheme('#f4f4f5', '#3f3f46')};
color: #ffffff; border-color: ${cssManager.bdTheme('#d1d5db', '#52525b')};
} }
.modal .bottomButtons .bottomButton:active { .modal .bottomButtons .bottomButton:active {
background: ${cssManager.bdTheme(colors.bright.blueActive, colors.dark.blueActive)}; background: ${cssManager.bdTheme('#e5e7eb', '#52525b')};
color: #ffffff;
} }
.modal .bottomButtons .bottomButton:last-child { .modal .bottomButtons .bottomButton:last-child {
border-right: none; border-right: none;
} }
.modal .bottomButtons .bottomButton.primary { .modal .bottomButtons .bottomButton.primary {
background: ${cssManager.bdTheme(colors.bright.blue, colors.dark.blue)}; background: ${cssManager.bdTheme('#3b82f6', '#3b82f6')};
border-color: ${cssManager.bdTheme('#3b82f6', '#3b82f6')};
color: #ffffff; color: #ffffff;
} }
.modal .bottomButtons .bottomButton.primary:hover { .modal .bottomButtons .bottomButton.primary:hover {
background: ${cssManager.bdTheme(colors.bright.blueActive, colors.dark.blueActive)}; background: ${cssManager.bdTheme('#2563eb', '#2563eb')};
border-color: ${cssManager.bdTheme('#2563eb', '#2563eb')};
} }
.modal .bottomButtons .bottomButton.primary:active { .modal .bottomButtons .bottomButton.primary:active {
background: ${cssManager.bdTheme(colors.bright.blueMuted, colors.dark.blueMuted)}; background: ${cssManager.bdTheme('#1d4ed8', '#1d4ed8')};
border-color: ${cssManager.bdTheme('#1d4ed8', '#1d4ed8')};
} }
`, `,
]; ];

View File

@ -1,5 +1,6 @@
import { demoFunc } from './dees-statsgrid.demo.js'; import { demoFunc } from './dees-statsgrid.demo.js';
import * as plugins from './00plugins.js'; import * as plugins from './00plugins.js';
import { cssGeistFontFamily } from './00fonts.js';
import { import {
customElement, customElement,
html, html,
@ -79,6 +80,7 @@ export class DeesStatsGrid extends DeesElement {
:host { :host {
display: block; display: block;
width: 100%; width: 100%;
font-family: ${cssGeistFontFamily};
} }
/* CSS Variables for consistent spacing and sizing */ /* CSS Variables for consistent spacing and sizing */
@ -194,7 +196,7 @@ export class DeesStatsGrid extends DeesElement {
font-size: var(--value-font-size); font-size: var(--value-font-size);
font-weight: 600; font-weight: 600;
color: ${cssManager.bdTheme('hsl(215.3 25% 8.8%)', 'hsl(210 40% 98%)')}; color: ${cssManager.bdTheme('hsl(215.3 25% 8.8%)', 'hsl(210 40% 98%)')};
line-height: 1; line-height: 1.1;
display: flex; display: flex;
align-items: baseline; align-items: baseline;
gap: 4px; gap: 4px;
@ -251,10 +253,10 @@ export class DeesStatsGrid extends DeesElement {
.gauge-text { .gauge-text {
fill: ${cssManager.bdTheme('hsl(215.3 25% 8.8%)', 'hsl(210 40% 98%)')}; fill: ${cssManager.bdTheme('hsl(215.3 25% 8.8%)', 'hsl(210 40% 98%)')};
font-family: ${cssGeistFontFamily};
font-size: var(--value-font-size); font-size: var(--value-font-size);
font-weight: 600; font-weight: 600;
text-anchor: middle; text-anchor: middle;
dominant-baseline: alphabetic;
letter-spacing: -0.025em; letter-spacing: -0.025em;
} }
@ -262,6 +264,7 @@ export class DeesStatsGrid extends DeesElement {
font-size: var(--unit-font-size); font-size: var(--unit-font-size);
fill: ${cssManager.bdTheme('hsl(215.4 16.3% 46.9%)', 'hsl(215 20.2% 65.1%)')}; fill: ${cssManager.bdTheme('hsl(215.4 16.3% 46.9%)', 'hsl(215 20.2% 65.1%)')};
font-weight: 400; font-weight: 400;
font-family: ${cssGeistFontFamily};
} }
/* Percentage Styles */ /* Percentage Styles */
@ -274,6 +277,7 @@ export class DeesStatsGrid extends DeesElement {
font-size: var(--value-font-size); font-size: var(--value-font-size);
font-weight: 600; font-weight: 600;
color: ${cssManager.bdTheme('hsl(215.3 25% 8.8%)', 'hsl(210 40% 98%)')}; color: ${cssManager.bdTheme('hsl(215.3 25% 8.8%)', 'hsl(210 40% 98%)')};
line-height: 1.1;
letter-spacing: -0.025em; letter-spacing: -0.025em;
margin-bottom: 8px; margin-bottom: 8px;
} }
@ -311,6 +315,7 @@ export class DeesStatsGrid extends DeesElement {
font-size: var(--value-font-size); font-size: var(--value-font-size);
font-weight: 600; font-weight: 600;
color: ${cssManager.bdTheme('hsl(215.3 25% 8.8%)', 'hsl(210 40% 98%)')}; color: ${cssManager.bdTheme('hsl(215.3 25% 8.8%)', 'hsl(210 40% 98%)')};
line-height: 1.1;
letter-spacing: -0.025em; letter-spacing: -0.025em;
} }
@ -358,6 +363,7 @@ export class DeesStatsGrid extends DeesElement {
font-size: var(--value-font-size); font-size: var(--value-font-size);
font-weight: 600; font-weight: 600;
color: ${cssManager.bdTheme('hsl(215.3 25% 8.8%)', 'hsl(210 40% 98%)')}; color: ${cssManager.bdTheme('hsl(215.3 25% 8.8%)', 'hsl(210 40% 98%)')};
line-height: 1.1;
letter-spacing: -0.025em; letter-spacing: -0.025em;
} }
@ -401,7 +407,7 @@ export class DeesStatsGrid extends DeesElement {
<dees-contextmenu <dees-contextmenu
.x=${this.contextMenuPosition.x} .x=${this.contextMenuPosition.x}
.y=${this.contextMenuPosition.y} .y=${this.contextMenuPosition.y}
.menuItems=${this.contextMenuActions} .menuItems=${this.contextMenuActions as any}
@clicked=${() => this.contextMenuVisible = false} @clicked=${() => this.contextMenuVisible = false}
></dees-contextmenu> ></dees-contextmenu>
` : ''} ` : ''}
@ -521,8 +527,8 @@ export class DeesStatsGrid extends DeesElement {
stroke-dashoffset="${strokeDashoffset}" stroke-dashoffset="${strokeDashoffset}"
/> />
<!-- Value text --> <!-- Value text -->
<text class="gauge-text" x="${centerX}" y="${centerY}"> <text class="gauge-text" x="${centerX}" y="${centerY - 8}" dominant-baseline="middle">
<tspan>${value}</tspan>${tile.unit ? html`<tspan class="gauge-unit" dx="4">${tile.unit}</tspan>` : ''} <tspan>${value}</tspan>${tile.unit ? html`<tspan class="gauge-unit" dx="2" dy="0">${tile.unit}</tspan>` : ''}
</text> </text>
</svg> </svg>
</div> </div>

View File

@ -1,6 +1,6 @@
import { type ITableAction } from './dees-table.js'; import { type ITableAction } from './dees-table.js';
import * as plugins from './00plugins.js'; import * as plugins from './00plugins.js';
import { html } from '@design.estate/dees-element'; import { html, css, cssManager } from '@design.estate/dees-element';
interface ITableDemoData { interface ITableDemoData {
date: string; date: string;
@ -10,16 +10,52 @@ interface ITableDemoData {
export const demoFunc = () => html` export const demoFunc = () => html`
<style> <style>
${css`
.demoWrapper { .demoWrapper {
box-sizing: border-box; box-sizing: border-box;
position: absolute; position: absolute;
width: 100%; width: 100%;
height: 100%; height: 100%;
padding: 20px; padding: 32px;
background: #000000; background: ${cssManager.bdTheme('hsl(0 0% 95%)', 'hsl(0 0% 5%)')};
overflow-y: auto;
} }
.demo-container {
max-width: 1200px;
margin: 0 auto;
}
.demo-section {
margin-bottom: 48px;
}
.demo-title {
font-size: 24px;
font-weight: 600;
margin-bottom: 8px;
color: ${cssManager.bdTheme('hsl(0 0% 9%)', 'hsl(0 0% 95%)')};
}
.demo-description {
font-size: 14px;
color: ${cssManager.bdTheme('hsl(215.4 16.3% 46.9%)', 'hsl(215 20.2% 65.1%)')};
margin-bottom: 24px;
}
.theme-toggle {
position: fixed;
top: 16px;
right: 16px;
z-index: 1000;
}
`}
</style> </style>
<div class="demoWrapper"> <div class="demoWrapper">
<dees-button class="theme-toggle" @click=${() => {
document.body.classList.toggle('bright');
document.body.classList.toggle('dark');
}}>Toggle Theme</dees-button>
<div class="demo-container">
<div class="demo-section">
<h2 class="demo-title">Basic Table with Actions</h2>
<p class="demo-description">A standard table with row actions, editable fields, and context menu support. Double-click on descriptions to edit. Grid lines are enabled by default.</p>
<dees-table <dees-table
heading1="Current Account Statement" heading1="Current Account Statement"
heading2="Bunq - Payment Account 2 - April 2021" heading2="Bunq - Payment Account 2 - April 2021"
@ -116,14 +152,281 @@ export const demoFunc = () => html`
return null; return null;
}, },
} }
] as (ITableAction<ITableDemoData>)[] as any}" ] as ITableAction[]}"
.displayFunction=${(itemArg) => { ></dees-table>
return { </div>
...itemArg,
onlyDisplayProp: 'onlyDisplay', <div class="demo-section">
}; <h2 class="demo-title">Table with Vertical Lines</h2>
}} <p class="demo-description">Enhanced column separation for better data tracking.</p>
>This is a slotted Text</dees-table <dees-table
> heading1="Product Inventory"
heading2="Current stock levels across warehouses"
.showVerticalLines=${true}
.data=${[
{
product: 'MacBook Pro 16"',
warehouse_a: '45',
warehouse_b: '32',
warehouse_c: '28',
total: '105',
status: '✓ In Stock'
},
{
product: 'iPhone 15 Pro',
warehouse_a: '120',
warehouse_b: '89',
warehouse_c: '156',
total: '365',
status: '✓ In Stock'
},
{
product: 'AirPods Pro',
warehouse_a: '0',
warehouse_b: '12',
warehouse_c: '5',
total: '17',
status: '⚠ Low Stock'
},
{
product: 'iPad Air',
warehouse_a: '23',
warehouse_b: '45',
warehouse_c: '67',
total: '135',
status: '✓ In Stock'
}
]}
dataName="products"
></dees-table>
</div>
<div class="demo-section">
<h2 class="demo-title">Table with Full Grid</h2>
<p class="demo-description">Complete grid lines for maximum readability and structure.</p>
<dees-table
heading1="Server Monitoring Dashboard"
heading2="Real-time metrics across regions"
.showGrid=${true}
.data=${[
{
server: 'API-1',
region: 'US-East',
cpu: '45%',
memory: '62%',
disk: '78%',
latency: '12ms',
uptime: '99.9%',
status: '🟢 Healthy'
},
{
server: 'API-2',
region: 'EU-West',
cpu: '38%',
memory: '55%',
disk: '45%',
latency: '25ms',
uptime: '99.8%',
status: '🟢 Healthy'
},
{
server: 'DB-Master',
region: 'US-East',
cpu: '72%',
memory: '81%',
disk: '92%',
latency: '8ms',
uptime: '100%',
status: '🟡 Warning'
},
{
server: 'DB-Replica',
region: 'EU-West',
cpu: '23%',
memory: '34%',
disk: '45%',
latency: '15ms',
uptime: '99.7%',
status: '🟢 Healthy'
},
{
server: 'Cache-1',
region: 'AP-South',
cpu: '89%',
memory: '92%',
disk: '12%',
latency: '120ms',
uptime: '98.5%',
status: '🔴 Critical'
}
]}
dataName="servers"
.dataActions="${[
{
name: 'SSH Connect',
iconName: 'lucide:terminal',
type: ['inRow'],
actionFunc: async (optionsArg) => {
console.log('Connecting to:', optionsArg.item.server);
},
},
{
name: 'View Logs',
iconName: 'lucide:file-text',
type: ['inRow', 'contextmenu'],
actionFunc: async (optionsArg) => {
console.log('Viewing logs for:', optionsArg.item.server);
},
},
{
name: 'Restart Server',
iconName: 'lucide:refresh-cw',
type: ['contextmenu'],
actionFunc: async (optionsArg) => {
console.log('Restarting:', optionsArg.item.server);
},
}
] as ITableAction[]}"
></dees-table>
</div>
<div class="demo-section">
<h2 class="demo-title">Table with Horizontal Lines Only</h2>
<p class="demo-description">Emphasis on row separation without column dividers.</p>
<dees-table
heading1="Sales Performance"
heading2="Top performers this quarter"
.showHorizontalLines=${true}
.showVerticalLines=${false}
.data=${[
{
salesperson: 'Emily Johnson',
region: 'North America',
deals_closed: '42',
revenue: '$1.2M',
quota_achievement: '128%',
rating: '⭐⭐⭐⭐⭐'
},
{
salesperson: 'Michael Chen',
region: 'Asia Pacific',
deals_closed: '38',
revenue: '$980K',
quota_achievement: '115%',
rating: '⭐⭐⭐⭐⭐'
},
{
salesperson: 'Sarah Williams',
region: 'Europe',
deals_closed: '35',
revenue: '$875K',
quota_achievement: '108%',
rating: '⭐⭐⭐⭐'
},
{
salesperson: 'David Garcia',
region: 'Latin America',
deals_closed: '31',
revenue: '$750K',
quota_achievement: '95%',
rating: '⭐⭐⭐⭐'
}
]}
dataName="sales reps"
></dees-table>
</div>
<div class="demo-section">
<h2 class="demo-title">Simple Table (No Grid)</h2>
<p class="demo-description">Clean, minimal design without grid lines. Set showGrid to false to disable the default grid.</p>
<dees-table
heading1="Team Members"
heading2="Engineering Department"
.showGrid=${false}
.data=${[
{
name: 'Alice Johnson',
role: 'Lead Engineer',
email: 'alice@company.com',
location: 'San Francisco',
joined: '2020-03-15'
},
{
name: 'Bob Smith',
role: 'Senior Developer',
email: 'bob@company.com',
location: 'New York',
joined: '2019-07-22'
},
{
name: 'Charlie Davis',
role: 'DevOps Engineer',
email: 'charlie@company.com',
location: 'London',
joined: '2021-01-10'
},
{
name: 'Diana Martinez',
role: 'Frontend Developer',
email: 'diana@company.com',
location: 'Barcelona',
joined: '2022-05-18'
}
]}
dataName="team members"
></dees-table>
</div>
<div class="demo-section">
<h2 class="demo-title">Table with Custom Display Function</h2>
<p class="demo-description">Transform data for display using custom formatting.</p>
<dees-table
heading1="Sales Report"
heading2="Q4 2023 Performance"
.data=${[
{
product: 'Enterprise License',
units: 45,
revenue: 225000,
growth: 0.23,
forecast: 280000
},
{
product: 'Professional License',
units: 128,
revenue: 128000,
growth: 0.15,
forecast: 147000
},
{
product: 'Starter License',
units: 342,
revenue: 68400,
growth: 0.42,
forecast: 97000
}
]}
.displayFunction=${(item) => ({
Product: item.product,
'Units Sold': item.units.toLocaleString(),
Revenue: '$' + item.revenue.toLocaleString(),
Growth: (item.growth * 100).toFixed(1) + '%',
'Q1 2024 Forecast': '$' + item.forecast.toLocaleString()
})}
dataName="products"
></dees-table>
</div>
<div class="demo-section">
<h2 class="demo-title">Empty Table State</h2>
<p class="demo-description">How the table looks when no data is available.</p>
<dees-table
heading1="No Data Available"
heading2="This table is currently empty"
.data=${[]}
dataName="items"
></dees-table>
</div>
</div>
</div> </div>
`; `;

View File

@ -1,6 +1,6 @@
import * as colors from './00colors.js';
import * as plugins from './00plugins.js'; import * as plugins from './00plugins.js';
import { demoFunc } from './dees-table.demo.js'; import { demoFunc } from './dees-table.demo.js';
import { cssGeistFontFamily } from './00fonts.js';
import { import {
customElement, customElement,
html, html,
@ -9,9 +9,6 @@ import {
type TemplateResult, type TemplateResult,
cssManager, cssManager,
css, css,
unsafeCSS,
type CSSResult,
state,
directives, directives,
} from '@design.estate/dees-element'; } from '@design.estate/dees-element';
@ -113,7 +110,7 @@ export class DeesTable<T> extends DeesElement {
get value() { get value() {
return this.data; return this.data;
} }
set value(valueArg) {} set value(_valueArg) {}
public changeSubject = new domtools.plugins.smartrx.rxjs.Subject<DeesTable<T>>(); public changeSubject = new domtools.plugins.smartrx.rxjs.Subject<DeesTable<T>>();
// end dees-form compatibility ----------------------------------------- // end dees-form compatibility -----------------------------------------
@ -157,6 +154,27 @@ export class DeesTable<T> extends DeesElement {
}) })
public editableFields: string[] = []; public editableFields: string[] = [];
@property({
type: Boolean,
reflect: true,
attribute: 'show-vertical-lines'
})
public showVerticalLines: boolean = false;
@property({
type: Boolean,
reflect: true,
attribute: 'show-horizontal-lines'
})
public showHorizontalLines: boolean = false;
@property({
type: Boolean,
reflect: true,
attribute: 'show-grid'
})
public showGrid: boolean = true;
public files: File[] = []; public files: File[] = [];
public fileWeakMap = new WeakMap(); public fileWeakMap = new WeakMap();
@ -169,238 +187,358 @@ export class DeesTable<T> extends DeesElement {
public static styles = [ public static styles = [
cssManager.defaultStyles, cssManager.defaultStyles,
css` css`
.mainbox { :host {
color: ${cssManager.bdTheme('#333', '#fff')};
font-family: 'Geist Sans', sans-serif;
font-weight: 400;
font-size: 14px;
padding: 16px;
display: block; display: block;
width: 100%; width: 100%;
min-height: 50px; }
background: ${cssManager.bdTheme('#ffffff', '#222222')};
border-radius: 3px; .mainbox {
border-top: 1px solid ${cssManager.bdTheme('#fff', '#ffffff10')}; color: ${cssManager.bdTheme('hsl(0 0% 3.9%)', 'hsl(0 0% 98%)')};
box-shadow: 0px 1px 4px rgba(0, 0, 0, 0.3); font-family: ${cssGeistFontFamily};
overflow-x: auto; font-weight: 400;
font-size: 14px;
display: block;
width: 100%;
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;
cursor: default; cursor: default;
} }
.header { .header {
display: flex; display: flex;
justify-content: flex-end; justify-content: space-between;
align-items: center; align-items: center;
font-family: 'Geist Sans', sans-serif; padding: 16px 24px;
min-height: 64px;
border-bottom: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')};
} }
.headingContainer { .headingContainer {
flex: 1;
} }
.heading { .heading {
line-height: 1.5;
} }
.heading1 { .heading1 {
font-size: 18px;
font-weight: 600; font-weight: 600;
color: ${cssManager.bdTheme('hsl(0 0% 9%)', 'hsl(0 0% 95%)')};
letter-spacing: -0.025em;
} }
.heading2 { .heading2 {
opacity: 0.6; font-size: 14px;
color: ${cssManager.bdTheme('hsl(215.4 16.3% 56.9%)', 'hsl(215 20.2% 55.1%)')};
margin-top: 2px;
} }
.headingSeparation { .headingSeparation {
margin-top: 7px; display: none;
border-bottom: 1px solid ${cssManager.bdTheme('#bcbcbc', '#444444')};
} }
.headerActions { .headerActions {
user-select: none; user-select: none;
display: flex; display: flex;
flex-direction: row; flex-direction: row;
margin-left: auto; gap: 8px;
} }
.headerAction { .headerAction {
display: flex; display: flex;
flex-direction: row; align-items: center;
color: ${cssManager.bdTheme('#333', '#ccc')}; gap: 6px;
margin-left: 16px; padding: 6px 12px;
font-size: 14px;
font-weight: 500;
color: ${cssManager.bdTheme('hsl(0 0% 45.1%)', 'hsl(0 0% 63.9%)')};
background: transparent;
border: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')};
border-radius: 6px;
cursor: pointer;
transition: all 0.15s ease;
} }
.headerAction:hover { .headerAction:hover {
color: ${cssManager.bdTheme('#555', '#fff')}; color: ${cssManager.bdTheme('hsl(0 0% 9%)', 'hsl(0 0% 95%)')};
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%)')};
} }
.headerAction dees-icon { .headerAction dees-icon {
margin-right: 8px; width: 14px;
height: 14px;
} }
.searchGrid { .searchGrid {
background: ${cssManager.bdTheme('#fff', '#111111')};
display: grid; display: grid;
grid-gap: 16px; grid-gap: 16px;
grid-template-columns: 1fr 200px; grid-template-columns: 1fr 200px;
margin-top: 16px; padding: 16px 24px;
padding: 0px 16px; background: ${cssManager.bdTheme('hsl(210 40% 98%)', 'hsl(0 0% 3.9%)')};
border-top: 1px solid ${cssManager.bdTheme('#fff', '#ffffff20')}; border-bottom: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')};
border-radius: 8px; transition: all 0.2s ease;
} }
.searchGrid.hidden { .searchGrid.hidden {
height: 0px; height: 0px;
opacity: 0; opacity: 0;
overflow: hidden; overflow: hidden;
margin-top: 0px; padding: 0px 24px;
border-bottom-width: 0px;
} }
table, table {
.noDataSet {
margin-top: 16px;
color: ${cssManager.bdTheme('#333', '#fff')};
border-collapse: collapse;
width: 100%; width: 100%;
} caption-side: bottom;
.noDataSet { font-size: 14px;
text-align: center; border-collapse: separate;
} border-spacing: 0;
tr {
border-bottom: 1px dashed ${cssManager.bdTheme('#999', '#808080')};
text-align: left;
}
tr:last-child {
border-bottom: none;
text-align: left;
}
tr:hover {
}
tr:hover td {
background: ${cssManager.bdTheme('#22222210', '#ffffff10')};
}
tr:first-child:hover {
cursor: auto;
}
tr:first-child:hover .innerCellContainer {
background: none;
}
tr.selected td {
background: ${cssManager.bdTheme('#22222220', '#ffffff20')};
} }
tr.hasAttachment td { .noDataSet {
background: ${cssManager.bdTheme('#0098847c', '#0098847c')}; padding: 48px 24px;
text-align: center;
color: ${cssManager.bdTheme('hsl(215.4 16.3% 56.9%)', 'hsl(215 20.2% 55.1%)')};
}
thead {
background: ${cssManager.bdTheme('hsl(210 40% 96.1%)', 'hsl(0 0% 9%)')};
border-bottom: 1px solid ${cssManager.bdTheme('hsl(0 0% 79.8%)', 'hsl(0 0% 20.9%)')};
}
tbody tr {
transition: background-color 0.15s ease;
position: relative;
}
/* Default horizontal lines (bottom border only) */
tbody tr {
border-bottom: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')};
}
tbody tr:last-child {
border-bottom: none;
}
/* Full horizontal lines when enabled */
:host([show-horizontal-lines]) tbody tr {
border-top: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')};
border-bottom: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')};
}
:host([show-horizontal-lines]) tbody tr:first-child {
border-top: none;
}
:host([show-horizontal-lines]) tbody tr:last-child {
border-bottom: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')};
}
tbody tr:hover {
background: ${cssManager.bdTheme('hsl(210 40% 96.1% / 0.5)', 'hsl(0 0% 14.9% / 0.5)')};
}
/* Column hover effect for better traceability */
td {
position: relative;
}
td::after {
content: '';
position: absolute;
top: -1000px;
bottom: -1000px;
left: 0;
right: 0;
background: ${cssManager.bdTheme('hsl(210 40% 96.1% / 0.3)', 'hsl(0 0% 14.9% / 0.3)')};
opacity: 0;
pointer-events: none;
transition: opacity 0.15s ease;
z-index: -1;
}
td:hover::after {
opacity: 1;
}
/* Grid mode - shows both vertical and horizontal lines */
:host([show-grid]) th {
border: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')};
border-left: none;
border-top: none;
}
:host([show-grid]) td {
border: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')};
border-left: none;
border-top: none;
}
:host([show-grid]) th:first-child,
:host([show-grid]) td:first-child {
border-left: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')};
}
:host([show-grid]) tbody tr:first-child td {
border-top: none;
}
tbody tr.selected {
background: ${cssManager.bdTheme('hsl(210 40% 96.1%)', 'hsl(0 0% 14.9%)')};
}
tbody tr.hasAttachment {
background: ${cssManager.bdTheme('hsl(142.1 76.2% 36.3% / 0.1)', 'hsl(142.1 76.2% 36.3% / 0.1)')};
} }
th { th {
text-transform: none; height: 48px;
font-family: 'Geist Sans', sans-serif; padding: 12px 24px;
text-align: left;
font-weight: 500; font-weight: 500;
color: ${cssManager.bdTheme('hsl(215.4 16.3% 46.9%)', 'hsl(215 20.2% 65.1%)')};
letter-spacing: -0.01em;
} }
th,
td {
position: relative;
vertical-align: top;
padding: 0px; :host([show-vertical-lines]) th {
border-right: 1px dashed ${cssManager.bdTheme('#999', '#808080')}; border-right: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')};
} }
.innerCellContainer {
min-height: 36px; td {
position: relative; padding: 12px 24px;
height: 100%; vertical-align: middle;
width: 100%; color: ${cssManager.bdTheme('hsl(0 0% 3.9%)', 'hsl(0 0% 98%)')};
padding: 6px 8px;
line-height: 24px;
} }
th:first-child .innerCellContainer,
td:first-child .innerCellContainer { :host([show-vertical-lines]) td {
padding-left: 0px; border-right: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')};
} }
th:last-child .innerCellContainer,
td:last-child .innerCellContainer { th:first-child,
padding-right: 0px; td:first-child {
padding-left: 24px;
} }
th:last-child, th:last-child,
td:last-child { td:last-child {
padding-right: 24px;
}
:host([show-vertical-lines]) th:last-child,
:host([show-vertical-lines]) td:last-child {
border-right: none; border-right: none;
} }
.innerCellContainer {
position: relative;
min-height: 24px;
line-height: 24px;
}
td input { td input {
width: 100%;
height: 100%;
outline: none;
border: 2px solid #fa6101;
top: 0px;
bottom: 0px;
right: 0px;
left: 0px;
position: absolute; position: absolute;
background: #fa610140; top: 4px;
color: ${cssManager.bdTheme('#333', '#fff')}; bottom: 4px;
left: 20px;
right: 20px;
width: calc(100% - 40px);
height: calc(100% - 8px);
padding: 0 12px;
outline: none;
border: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')};
border-radius: 6px;
background: ${cssManager.bdTheme('hsl(0 0% 100%)', 'hsl(0 0% 9%)')};
color: ${cssManager.bdTheme('hsl(0 0% 3.9%)', 'hsl(0 0% 98%)')};
font-family: inherit; font-family: inherit;
font-size: inherit; font-size: inherit;
font-weight: inherit; font-weight: inherit;
padding: 0px 6px; transition: all 0.15s ease;
box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);
}
td input:focus {
border-color: ${cssManager.bdTheme('hsl(222.2 47.4% 51.2%)', 'hsl(217.2 91.2% 59.8%)')};
outline: 2px solid transparent;
outline-offset: 2px;
box-shadow: 0 0 0 2px ${cssManager.bdTheme('hsl(222.2 47.4% 51.2% / 0.2)', 'hsl(217.2 91.2% 59.8% / 0.2)')};
} }
.actionsContainer { .actionsContainer {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
height: 24px; gap: 4px;
transform: translateY(-4px);
margin-left: -6px;
} }
.action { .action {
position: relative; display: flex;
padding: 8px 10px; align-items: center;
line-height: 24px; justify-content: center;
width: 32px;
height: 32px; height: 32px;
size: 16px; border-radius: 6px;
border-radius: 8px; color: ${cssManager.bdTheme('hsl(215.4 16.3% 46.9%)', 'hsl(215 20.2% 65.1%)')};
cursor: pointer;
transition: all 0.15s ease;
} }
.action:hover { .action:hover {
background: ${cssManager.bdTheme(colors.bright.blue, colors.dark.blue)}; background: ${cssManager.bdTheme('hsl(210 40% 96.1%)', 'hsl(0 0% 14.9%)')};
color: ${cssManager.bdTheme('hsl(0 0% 9%)', 'hsl(0 0% 95%)')};
} }
.action:active { .action:active {
background: ${cssManager.bdTheme(colors.bright.blue, colors.dark.blueActive)}; background: ${cssManager.bdTheme('hsl(210 40% 96.1%)', 'hsl(0 0% 11.8%)')};
} }
.action:hover dees-icon { .action dees-icon {
filter: ${cssManager.bdTheme('invert(1) brightness(3)', '')}; width: 16px;
height: 16px;
} }
.footer { .footer {
font-family: 'Geist Sans', sans-serif;
font-size: 14px;
color: ${cssManager.bdTheme('#111', '#ffffff90')};
background: ${cssManager.bdTheme('#eeeeeb', '#00000050')};
margin: 16px -16px -16px -16px;
border-bottom-left-radius: 3px;
border-bottom-right-radius: 3px;
display: flex; display: flex;
align-items: center;
justify-content: space-between;
height: 52px;
padding: 0 24px;
font-size: 14px;
color: ${cssManager.bdTheme('hsl(215.4 16.3% 46.9%)', 'hsl(215 20.2% 65.1%)')};
background: ${cssManager.bdTheme('hsl(210 40% 96.1%)', 'hsl(0 0% 9%)')};
border-top: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')};
} }
.tableStatistics { .tableStatistics {
padding: 8px 16px; font-weight: 500;
} }
.footerActions { .footerActions {
margin-left: auto; display: flex;
gap: 8px;
} }
.footerActions .footerAction { .footerActions .footerAction {
padding: 8px 16px;
display: flex; display: flex;
align-items: center;
gap: 6px;
padding: 6px 12px;
font-weight: 500;
color: ${cssManager.bdTheme('hsl(215.4 16.3% 46.9%)', 'hsl(215 20.2% 65.1%)')};
border-radius: 6px;
cursor: pointer;
user-select: none; user-select: none;
transition: all 0.15s ease;
} }
.footerActions .footerAction:hover { .footerActions .footerAction:hover {
background: ${cssManager.bdTheme(colors.bright.blue, colors.dark.blue)}; background: ${cssManager.bdTheme('hsl(0 0% 95.1%)', 'hsl(0 0% 14.9%)')};
color: #fff; color: ${cssManager.bdTheme('hsl(0 0% 9%)', 'hsl(0 0% 95%)')};
} }
.footerActions .footerAction dees-icon { .footerActions .footerAction dees-icon {
display: flex; width: 14px;
margin-right: 8px; height: 14px;
}
.footerActions .footerAction:hover dees-icon {
} }
`, `,
]; ];
@ -478,24 +616,23 @@ export class DeesTable<T> extends DeesElement {
const headings: string[] = Object.keys(firstTransformedItem); const headings: string[] = Object.keys(firstTransformedItem);
return html` return html`
<table> <table>
<thead>
<tr> <tr>
${headings.map( ${headings.map(
(headingArg) => html` (headingArg) => html`
<th> <th>${headingArg}</th>
<div class="innerCellContainer">${headingArg}</div>
</th>
` `
)} )}
${(() => { ${(() => {
if (this.dataActions && this.dataActions.length > 0) { if (this.dataActions && this.dataActions.length > 0) {
return html` return html`
<th> <th>Actions</th>
<div class="innerCellContainer">Actions</div>
</th>
`; `;
} }
})()} })()}
</tr> </tr>
</thead>
<tbody>
${this.data.map((itemArg) => { ${this.data.map((itemArg) => {
const transformedItem = this.displayFunction(itemArg); const transformedItem = this.displayFunction(itemArg);
const getTr = (elementArg: HTMLElement): HTMLElement => { const getTr = (elementArg: HTMLElement): HTMLElement => {
@ -592,7 +729,6 @@ export class DeesTable<T> extends DeesElement {
if (this.dataActions && this.dataActions.length > 0) { if (this.dataActions && this.dataActions.length > 0) {
return html` return html`
<td> <td>
<div class="innerCellContainer">
<div class="actionsContainer"> <div class="actionsContainer">
${this.getActionsForType('inRow').map( ${this.getActionsForType('inRow').map(
(actionArg) => html` (actionArg) => html`
@ -615,7 +751,6 @@ export class DeesTable<T> extends DeesElement {
` `
)} )}
</div> </div>
</div>
</td> </td>
`; `;
} }
@ -623,6 +758,7 @@ export class DeesTable<T> extends DeesElement {
</tr> </tr>
`; `;
})} })}
</tbody>
</table> </table>
`; `;
})() })()
@ -743,7 +879,7 @@ export class DeesTable<T> extends DeesElement {
} }
async handleCellEditing(event: Event, itemArg: T, key: string) { async handleCellEditing(event: Event, itemArg: T, key: string) {
const domtools = await this.domtoolsPromise; await this.domtoolsPromise;
const target = event.target as HTMLElement; const target = event.target as HTMLElement;
const originalColor = target.style.color; const originalColor = target.style.color;
target.style.color = 'transparent'; target.style.color = 'transparent';

View File

@ -3,6 +3,7 @@ import { customElement, DeesElement, type TemplateResult, html, css, property, c
import * as domtools from '@design.estate/dees-domtools'; import * as domtools from '@design.estate/dees-domtools';
import { zIndexLayers } from './00zindex.js'; import { zIndexLayers } from './00zindex.js';
import { demoFunc } from './dees-toast.demo.js'; import { demoFunc } from './dees-toast.demo.js';
import { cssGeistFontFamily } from './00fonts.js';
declare global { declare global {
interface HTMLElementTagNameMap { interface HTMLElementTagNameMap {
@ -152,7 +153,7 @@ export class DeesToast extends DeesElement {
:host { :host {
display: block; display: block;
pointer-events: auto; pointer-events: auto;
font-family: 'Geist Sans', sans-serif; font-family: ${cssGeistFontFamily};
opacity: 0; opacity: 0;
transform: translateY(-10px); transform: translateY(-10px);
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);

View File

@ -3,6 +3,8 @@ import type { IBlock } from '../../wysiwyg.types.js';
import { cssManager } from '@design.estate/dees-element'; import { cssManager } from '@design.estate/dees-element';
import { WysiwygSelection } from '../../wysiwyg.selection.js'; import { WysiwygSelection } from '../../wysiwyg.selection.js';
import hlight from 'highlight.js'; import hlight from 'highlight.js';
import { cssGeistFontFamily, cssMonoFontFamily } from '../../../00fonts.js';
import { PROGRAMMING_LANGUAGES } from '../../wysiwyg.constants.js';
/** /**
* CodeBlockHandler with improved architecture * CodeBlockHandler with improved architecture
@ -20,7 +22,7 @@ export class CodeBlockHandler extends BaseBlockHandler {
private highlightTimer: any = null; private highlightTimer: any = null;
render(block: IBlock, isSelected: boolean): string { render(block: IBlock, isSelected: boolean): string {
const language = block.metadata?.language || 'javascript'; const language = block.metadata?.language || 'typescript';
const content = block.content || ''; const content = block.content || '';
const lineCount = content.split('\n').length; const lineCount = content.split('\n').length;
@ -30,10 +32,18 @@ export class CodeBlockHandler extends BaseBlockHandler {
lineNumbersHtml += `<div class="line-number">${i}</div>`; lineNumbersHtml += `<div class="line-number">${i}</div>`;
} }
// Generate language options
const languageOptions = PROGRAMMING_LANGUAGES.map(lang => {
const value = lang.toLowerCase();
return `<option value="${value}" ${value === language ? 'selected' : ''}>${lang}</option>`;
}).join('');
return ` return `
<div class="code-block-container${isSelected ? ' selected' : ''}" data-language="${language}"> <div class="code-block-container${isSelected ? ' selected' : ''}" data-language="${language}">
<div class="code-header"> <div class="code-header">
<span class="language-label">${language}</span> <select class="language-selector" data-block-id="${block.id}">
${languageOptions}
</select>
<button class="copy-button" title="Copy code"> <button class="copy-button" title="Copy code">
<svg class="copy-icon" width="16" height="16" viewBox="0 0 16 16" fill="currentColor"> <svg class="copy-icon" width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
<path d="M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 010 1.5h-1.5a.25.25 0 00-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 00.25-.25v-1.5a.75.75 0 011.5 0v1.5A1.75 1.75 0 019.25 16h-7.5A1.75 1.75 0 010 14.25v-7.5z"></path> <path d="M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 010 1.5h-1.5a.25.25 0 00-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 00.25-.25v-1.5a.75.75 0 011.5 0v1.5A1.75 1.75 0 019.25 16h-7.5A1.75 1.75 0 010 14.25v-7.5z"></path>
@ -60,9 +70,29 @@ export class CodeBlockHandler extends BaseBlockHandler {
const editor = element.querySelector('.code-editor') as HTMLElement; const editor = element.querySelector('.code-editor') as HTMLElement;
const container = element.querySelector('.code-block-container') as HTMLElement; const container = element.querySelector('.code-block-container') as HTMLElement;
const copyButton = element.querySelector('.copy-button') as HTMLButtonElement; const copyButton = element.querySelector('.copy-button') as HTMLButtonElement;
const languageSelector = element.querySelector('.language-selector') as HTMLSelectElement;
if (!editor || !container) return; if (!editor || !container) return;
// Setup language selector
if (languageSelector) {
languageSelector.addEventListener('change', (e) => {
const newLanguage = (e.target as HTMLSelectElement).value;
block.metadata = { ...block.metadata, language: newLanguage };
container.setAttribute('data-language', newLanguage);
// Update the syntax highlighting if content exists and not focused
if (block.content && document.activeElement !== editor) {
this.applyHighlighting(element, block);
}
// Notify about the change
if (handlers.onInput) {
handlers.onInput(new InputEvent('input'));
}
});
}
// Setup copy button // Setup copy button
if (copyButton) { if (copyButton) {
copyButton.addEventListener('click', async () => { copyButton.addEventListener('click', async () => {
@ -285,7 +315,7 @@ export class CodeBlockHandler extends BaseBlockHandler {
// Get plain text content // Get plain text content
const content = editor.textContent || ''; const content = editor.textContent || '';
const language = block.metadata?.language || 'javascript'; const language = block.metadata?.language || 'typescript';
// Apply highlighting // Apply highlighting
try { try {
@ -336,7 +366,7 @@ export class CodeBlockHandler extends BaseBlockHandler {
type: 'code', type: 'code',
content: content, content: content,
metadata: { metadata: {
language: element.querySelector('.code-block-container')?.getAttribute('data-language') || 'javascript' language: element.querySelector('.code-block-container')?.getAttribute('data-language') || 'typescript'
} }
}; };
this.applyHighlighting(element, block); this.applyHighlighting(element, block);
@ -440,13 +470,30 @@ export class CodeBlockHandler extends BaseBlockHandler {
align-items: center; align-items: center;
} }
.language-label { .language-selector {
font-size: 12px; font-size: 12px;
color: ${cssManager.bdTheme('#6b7280', '#9ca3af')}; color: ${cssManager.bdTheme('#6b7280', '#9ca3af')};
font-weight: 500; font-weight: 500;
text-transform: uppercase; text-transform: uppercase;
letter-spacing: 0.05em; letter-spacing: 0.05em;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; font-family: ${cssGeistFontFamily};
background: transparent;
border: 1px solid transparent;
border-radius: 4px;
padding: 4px 8px;
cursor: pointer;
transition: all 0.15s ease;
outline: none;
}
.language-selector:hover {
background: ${cssManager.bdTheme('#f9fafb', '#1f2937')};
border-color: ${cssManager.bdTheme('#e5e7eb', '#374151')};
color: ${cssManager.bdTheme('#374151', '#e5e7eb')};
}
.language-selector:focus {
border-color: ${cssManager.bdTheme('#9ca3af', '#6b7280')};
} }
/* Copy Button - Minimal */ /* Copy Button - Minimal */
@ -460,7 +507,7 @@ export class CodeBlockHandler extends BaseBlockHandler {
border-radius: 4px; border-radius: 4px;
color: ${cssManager.bdTheme('#6b7280', '#9ca3af')}; color: ${cssManager.bdTheme('#6b7280', '#9ca3af')};
font-size: 12px; font-size: 12px;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; font-family: ${cssGeistFontFamily};
cursor: pointer; cursor: pointer;
transition: all 0.15s ease; transition: all 0.15s ease;
outline: none; outline: none;
@ -515,7 +562,7 @@ export class CodeBlockHandler extends BaseBlockHandler {
.line-number { .line-number {
padding: 0 12px 0 8px; padding: 0 12px 0 8px;
color: ${cssManager.bdTheme('#9ca3af', '#4b5563')}; color: ${cssManager.bdTheme('#9ca3af', '#4b5563')};
font-family: 'SF Mono', Monaco, Consolas, monospace; font-family: ${cssMonoFontFamily};
font-size: 13px; font-size: 13px;
line-height: 20px; line-height: 20px;
height: 20px; height: 20px;
@ -538,7 +585,7 @@ export class CodeBlockHandler extends BaseBlockHandler {
display: block; display: block;
padding: 12px 16px; padding: 12px 16px;
margin: 0; margin: 0;
font-family: 'SF Mono', Monaco, Consolas, monospace; font-family: ${cssMonoFontFamily};
font-size: 13px; font-size: 13px;
line-height: 20px; line-height: 20px;
color: ${cssManager.bdTheme('#111827', '#f9fafb')}; color: ${cssManager.bdTheme('#111827', '#f9fafb')};

View File

@ -235,6 +235,7 @@ export class DeesInputWysiwyg extends DeesInputBase<string> {
const blockComponent = document.createElement('dees-wysiwyg-block') as any; const blockComponent = document.createElement('dees-wysiwyg-block') as any;
blockComponent.block = block; blockComponent.block = block;
blockComponent.isSelected = this.selectedBlockId === block.id; blockComponent.isSelected = this.selectedBlockId === block.id;
blockComponent.wysiwygComponent = this; // Pass parent reference
blockComponent.handlers = { blockComponent.handlers = {
onInput: (e: InputEvent) => this.inputHandler.handleBlockInput(e, block), onInput: (e: InputEvent) => this.inputHandler.handleBlockInput(e, block),
onKeyDown: (e: KeyboardEvent) => this.keyboardHandler.handleBlockKeyDown(e, block), onKeyDown: (e: KeyboardEvent) => this.keyboardHandler.handleBlockKeyDown(e, block),
@ -247,28 +248,7 @@ export class DeesInputWysiwyg extends DeesInputBase<string> {
}; };
wrapper.appendChild(blockComponent); wrapper.appendChild(blockComponent);
// Add settings button for non-divider blocks // Remove settings button - context menu will handle this
if (block.type !== 'divider') {
const settings = document.createElement('div');
settings.className = 'block-settings';
settings.innerHTML = `
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor">
<circle cx="12" cy="5" r="2"></circle>
<circle cx="12" cy="12" r="2"></circle>
<circle cx="12" cy="19" r="2"></circle>
</svg>
`;
settings.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
WysiwygModalManager.showBlockSettingsModal(block, () => {
this.updateValue();
// Re-render only the updated block
this.updateBlockElement(block.id);
});
});
wrapper.appendChild(settings);
}
// Add drag event listeners // Add drag event listeners
wrapper.addEventListener('dragover', (e) => this.dragDropHandler.handleDragOver(e, block)); wrapper.addEventListener('dragover', (e) => this.dragDropHandler.handleDragOver(e, block));
@ -507,13 +487,9 @@ export class DeesInputWysiwyg extends DeesInputBase<string> {
// Close menu // Close menu
this.closeSlashMenu(false); this.closeSlashMenu(false);
// If it's a code block, ask for language // If it's a code block, default to TypeScript
if (type === 'code') { if (type === 'code') {
const language = await WysiwygModalManager.showLanguageSelectionModal(); currentBlock.metadata = { language: 'typescript' };
if (!language) {
return; // User cancelled
}
currentBlock.metadata = { language };
} }
// Transform the current block // Transform the current block

View File

@ -8,6 +8,7 @@ import {
state, state,
} from '@design.estate/dees-element'; } from '@design.estate/dees-element';
import { zIndexRegistry } from '../00zindex.js'; import { zIndexRegistry } from '../00zindex.js';
import '../dees-icon.js';
import { type ISlashMenuItem } from './wysiwyg.types.js'; import { type ISlashMenuItem } from './wysiwyg.types.js';
import { WysiwygShortcuts } from './wysiwyg.shortcuts.js'; import { WysiwygShortcuts } from './wysiwyg.shortcuts.js';
@ -61,10 +62,10 @@ export class DeesSlashMenu extends DeesElement {
.slash-menu { .slash-menu {
position: fixed; position: fixed;
background: ${cssManager.bdTheme('#ffffff', '#262626')}; background: ${cssManager.bdTheme('#ffffff', '#09090b')};
border: 1px solid ${cssManager.bdTheme('#e0e0e0', '#404040')}; border: 1px solid ${cssManager.bdTheme('#e5e7eb', '#27272a')};
border-radius: 8px; border-radius: 4px;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12); box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1), 0 1px 2px rgba(0, 0, 0, 0.06);
padding: 4px; padding: 4px;
min-width: 220px; min-width: 220px;
max-height: 300px; max-height: 300px;
@ -77,7 +78,7 @@ export class DeesSlashMenu extends DeesElement {
@keyframes fadeInScale { @keyframes fadeInScale {
from { from {
opacity: 0; opacity: 0;
transform: scale(0.95) translateY(-10px); transform: scale(0.98) translateY(-2px);
} }
to { to {
opacity: 1; opacity: 1;
@ -86,37 +87,35 @@ export class DeesSlashMenu extends DeesElement {
} }
.slash-menu-item { .slash-menu-item {
padding: 10px 12px; padding: 8px 10px;
cursor: pointer; cursor: pointer;
transition: all 0.15s ease; transition: all 0.15s ease;
display: flex; display: flex;
align-items: center; align-items: center;
gap: 12px; gap: 12px;
border-radius: 4px; border-radius: 3px;
color: ${cssManager.bdTheme('#000000', '#e0e0e0')}; color: ${cssManager.bdTheme('#09090b', '#fafafa')};
font-size: 14px; font-size: 14px;
} }
.slash-menu-item:hover, .slash-menu-item:hover,
.slash-menu-item.selected { .slash-menu-item.selected {
background: ${cssManager.bdTheme('#f0f0f0', '#333333')}; background: ${cssManager.bdTheme('#f4f4f5', '#27272a')};
color: ${cssManager.bdTheme('#000000', '#ffffff')}; color: ${cssManager.bdTheme('#09090b', '#fafafa')};
} }
.slash-menu-item .icon { .slash-menu-item .icon {
width: 24px; width: 20px;
height: 24px; height: 20px;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
font-size: 16px; color: ${cssManager.bdTheme('#71717a', '#a1a1aa')};
color: ${cssManager.bdTheme('#666', '#999')};
font-weight: 600;
} }
.slash-menu-item:hover .icon, .slash-menu-item:hover .icon,
.slash-menu-item.selected .icon { .slash-menu-item.selected .icon {
color: ${cssManager.bdTheme('#0066cc', '#4d94ff')}; color: ${cssManager.bdTheme('#3b82f6', '#3b82f6')};
} }
`, `,
]; ];
@ -142,7 +141,7 @@ export class DeesSlashMenu extends DeesElement {
data-item-type="${item.type}" data-item-type="${item.type}"
data-item-index="${index}" data-item-index="${index}"
> >
<span class="icon">${item.icon}</span> <dees-icon class="icon" .icon="${item.icon}" iconSize="16"></dees-icon>
<span>${item.label}</span> <span>${item.label}</span>
</div> </div>
`)} `)}

View File

@ -13,6 +13,8 @@ import { WysiwygBlocks } from './wysiwyg.blocks.js';
import { WysiwygSelection } from './wysiwyg.selection.js'; import { WysiwygSelection } from './wysiwyg.selection.js';
import { BlockRegistry, type IBlockEventHandlers } from './blocks/index.js'; import { BlockRegistry, type IBlockEventHandlers } from './blocks/index.js';
import './wysiwyg.blockregistration.js'; import './wysiwyg.blockregistration.js';
import { WysiwygShortcuts } from './wysiwyg.shortcuts.js';
import '../dees-contextmenu.js';
declare global { declare global {
interface HTMLElementTagNameMap { interface HTMLElementTagNameMap {
@ -38,6 +40,9 @@ export class DeesWysiwygBlock extends DeesElement {
@property({ type: Object }) @property({ type: Object })
public handlers: IBlockEventHandlers; public handlers: IBlockEventHandlers;
@property({ type: Object })
public wysiwygComponent: any; // Reference to parent dees-input-wysiwyg
// Reference to the editable block element // Reference to the editable block element
private blockElement: HTMLDivElement | null = null; private blockElement: HTMLDivElement | null = null;
@ -685,6 +690,125 @@ export class DeesWysiwygBlock extends DeesElement {
/**
* Get context menu items for this block
*/
public getContextMenuItems(): any[] {
if (!this.block || this.block.type === 'divider') {
return [];
}
const blockTypes = WysiwygShortcuts.getSlashMenuItems();
const currentType = this.block.type;
// Use the parent reference passed from dees-input-wysiwyg
const wysiwygComponent = this.wysiwygComponent;
const blockId = this.block.id;
// Create submenu items for block type change
const blockTypeItems = blockTypes
.filter(item => item.type !== currentType && item.type !== 'divider')
.map(item => ({
name: item.label,
iconName: item.icon.replace('lucide:', ''),
action: async () => {
if (wysiwygComponent && wysiwygComponent.blockOperations) {
// Transform the block type
const blockToTransform = wysiwygComponent.blocks.find((b: IBlock) => b.id === blockId);
if (blockToTransform) {
blockToTransform.type = item.type;
blockToTransform.content = blockToTransform.content || '';
// Handle special metadata for different block types
if (item.type === 'code') {
blockToTransform.metadata = { language: 'typescript' };
} else if (item.type === 'list') {
blockToTransform.metadata = { listType: 'bullet' };
} else if (item.type === 'image') {
blockToTransform.content = '';
blockToTransform.metadata = { url: '', loading: false };
} else if (item.type === 'youtube') {
blockToTransform.content = '';
blockToTransform.metadata = { videoId: '', url: '' };
} else if (item.type === 'markdown') {
blockToTransform.metadata = { showPreview: false };
} else if (item.type === 'html') {
blockToTransform.metadata = { showPreview: false };
} else if (item.type === 'attachment') {
blockToTransform.content = '';
blockToTransform.metadata = { files: [] };
}
// Update the block element
wysiwygComponent.updateBlockElement(blockId);
wysiwygComponent.updateValue();
// Focus the block after transformation
requestAnimationFrame(() => {
wysiwygComponent.blockOperations.focusBlock(blockId);
});
}
}
}
}));
const menuItems: any[] = [
{
name: 'Change Type',
iconName: 'type',
submenu: blockTypeItems
}
];
// Add copy/cut/paste for editable blocks
if (!['image', 'divider', 'youtube', 'attachment'].includes(this.block.type)) {
menuItems.push(
{ divider: true },
{
name: 'Cut',
iconName: 'scissors',
shortcut: 'Cmd+X',
action: async () => {
document.execCommand('cut');
}
},
{
name: 'Copy',
iconName: 'copy',
shortcut: 'Cmd+C',
action: async () => {
document.execCommand('copy');
}
},
{
name: 'Paste',
iconName: 'clipboard',
shortcut: 'Cmd+V',
action: async () => {
document.execCommand('paste');
}
}
);
}
// Add delete option
menuItems.push(
{ divider: true },
{
name: 'Delete Block',
iconName: 'trash2',
action: async () => {
if (wysiwygComponent && wysiwygComponent.blockOperations) {
wysiwygComponent.blockOperations.deleteBlock(blockId);
}
}
}
);
return menuItems;
}
/** /**
* Gets content split at cursor position * Gets content split at cursor position
*/ */

View File

@ -49,19 +49,19 @@ export class WysiwygShortcuts {
static getSlashMenuItems(): ISlashMenuItem[] { static getSlashMenuItems(): ISlashMenuItem[] {
return [ return [
{ type: 'paragraph', label: 'Paragraph', icon: '' }, { type: 'paragraph', label: 'Paragraph', icon: 'lucide:pilcrow' },
{ type: 'heading-1', label: 'Heading 1', icon: 'H₁' }, { type: 'heading-1', label: 'Heading 1', icon: 'lucide:heading1' },
{ type: 'heading-2', label: 'Heading 2', icon: 'H₂' }, { type: 'heading-2', label: 'Heading 2', icon: 'lucide:heading2' },
{ type: 'heading-3', label: 'Heading 3', icon: 'H₃' }, { type: 'heading-3', label: 'Heading 3', icon: 'lucide:heading3' },
{ type: 'quote', label: 'Quote', icon: '"' }, { type: 'quote', label: 'Quote', icon: 'lucide:quote' },
{ type: 'code', label: 'Code', icon: '<>' }, { type: 'code', label: 'Code Block', icon: 'lucide:fileCode' },
{ type: 'list', label: 'List', icon: '' }, { type: 'list', label: 'Bullet List', icon: 'lucide:list' },
{ type: 'image', label: 'Image', icon: '🖼' }, { type: 'image', label: 'Image', icon: 'lucide:image' },
{ type: 'divider', label: 'Divider', icon: '' }, { type: 'divider', label: 'Divider', icon: 'lucide:minus' },
{ type: 'youtube', label: 'YouTube', icon: '▶️' }, { type: 'youtube', label: 'YouTube', icon: 'lucide:youtube' },
{ type: 'markdown', label: 'Markdown', icon: 'M↓' }, { type: 'markdown', label: 'Markdown', icon: 'lucide:fileText' },
{ type: 'html', label: 'HTML', icon: '</>' }, { type: 'html', label: 'HTML', icon: 'lucide:code' },
{ type: 'attachment', label: 'File Attachment', icon: '📎' }, { type: 'attachment', label: 'File Attachment', icon: 'lucide:paperclip' },
]; ];
} }

View File

@ -7,23 +7,25 @@ export const wysiwygStyles = css`
} }
.wysiwyg-container { .wysiwyg-container {
background: ${cssManager.bdTheme('#ffffff', '#1a1a1a')}; background: ${cssManager.bdTheme('#ffffff', '#09090b')};
border: 1px solid ${cssManager.bdTheme('#e0e0e0', '#333')}; border: 1px solid ${cssManager.bdTheme('#e5e7eb', '#27272a')};
border-radius: 8px; border-radius: 6px;
min-height: 200px; min-height: 200px;
padding: 32px 40px; padding: 24px;
position: relative; position: relative;
transition: all 0.2s ease; transition: all 0.2s ease;
color: ${cssManager.bdTheme('#000000', '#ffffff')}; color: ${cssManager.bdTheme('#09090b', '#fafafa')};
} }
.wysiwyg-container:hover { .wysiwyg-container:hover {
border-color: ${cssManager.bdTheme('#d0d0d0', '#444')}; border-color: ${cssManager.bdTheme('#d1d5db', '#3f3f46')};
} }
.wysiwyg-container:focus-within { .wysiwyg-container:focus-within {
border-color: ${cssManager.bdTheme('#0066cc', '#4d94ff')}; outline: 2px solid transparent;
box-shadow: 0 0 0 3px ${cssManager.bdTheme('rgba(0, 102, 204, 0.1)', 'rgba(77, 148, 255, 0.1)')}; outline-offset: 2px;
box-shadow: 0 0 0 2px ${cssManager.bdTheme('#f4f4f5', '#18181b')}, 0 0 0 4px ${cssManager.bdTheme('rgba(59, 130, 246, 0.5)', 'rgba(59, 130, 246, 0.5)')};
border-color: ${cssManager.bdTheme('#3b82f6', '#3b82f6')};
} }
/* Visual hint for text selection */ /* Visual hint for text selection */
@ -44,7 +46,7 @@ export const wysiwygStyles = css`
position: relative; position: relative;
transition: all 0.15s ease; transition: all 0.15s ease;
min-height: 1.6em; min-height: 1.6em;
color: ${cssManager.bdTheme('#000000', '#e0e0e0')}; color: ${cssManager.bdTheme('#09090b', '#fafafa')};
} }
/* First and last blocks don't need extra spacing */ /* First and last blocks don't need extra spacing */
@ -57,8 +59,9 @@ export const wysiwygStyles = css`
} }
.block.selected { .block.selected {
background: ${cssManager.bdTheme('rgba(0, 102, 204, 0.05)', 'rgba(77, 148, 255, 0.08)')}; background: ${cssManager.bdTheme('rgba(59, 130, 246, 0.05)', 'rgba(59, 130, 246, 0.05)')};
box-shadow: inset 0 0 0 2px ${cssManager.bdTheme('rgba(0, 102, 204, 0.2)', 'rgba(77, 148, 255, 0.2)')}; outline: 2px solid ${cssManager.bdTheme('rgba(59, 130, 246, 0.2)', 'rgba(59, 130, 246, 0.2)')};
outline-offset: -2px;
border-radius: 4px; border-radius: 4px;
margin-left: -8px; margin-left: -8px;
margin-right: -8px; margin-right: -8px;
@ -78,7 +81,7 @@ export const wysiwygStyles = css`
.block.paragraph:empty::before { .block.paragraph:empty::before {
content: "Type '/' for commands..."; content: "Type '/' for commands...";
color: ${cssManager.bdTheme('#999', '#666')}; color: ${cssManager.bdTheme('#71717a', '#71717a')};
pointer-events: none; pointer-events: none;
font-size: 16px; font-size: 16px;
line-height: 1.6; line-height: 1.6;
@ -89,12 +92,12 @@ export const wysiwygStyles = css`
font-size: 32px; font-size: 32px;
font-weight: 700; font-weight: 700;
line-height: 1.2; line-height: 1.2;
color: ${cssManager.bdTheme('#000000', '#ffffff')}; color: ${cssManager.bdTheme('#09090b', '#fafafa')};
} }
.block.heading-1:empty::before { .block.heading-1:empty::before {
content: "Heading 1"; content: "Heading 1";
color: ${cssManager.bdTheme('#999', '#666')}; color: ${cssManager.bdTheme('#71717a', '#71717a')};
pointer-events: none; pointer-events: none;
font-size: 32px; font-size: 32px;
line-height: 1.2; line-height: 1.2;
@ -105,12 +108,12 @@ export const wysiwygStyles = css`
font-size: 24px; font-size: 24px;
font-weight: 600; font-weight: 600;
line-height: 1.3; line-height: 1.3;
color: ${cssManager.bdTheme('#000000', '#ffffff')}; color: ${cssManager.bdTheme('#09090b', '#fafafa')};
} }
.block.heading-2:empty::before { .block.heading-2:empty::before {
content: "Heading 2"; content: "Heading 2";
color: ${cssManager.bdTheme('#999', '#666')}; color: ${cssManager.bdTheme('#71717a', '#71717a')};
pointer-events: none; pointer-events: none;
font-size: 24px; font-size: 24px;
line-height: 1.3; line-height: 1.3;
@ -121,12 +124,12 @@ export const wysiwygStyles = css`
font-size: 20px; font-size: 20px;
font-weight: 600; font-weight: 600;
line-height: 1.4; line-height: 1.4;
color: ${cssManager.bdTheme('#000000', '#ffffff')}; color: ${cssManager.bdTheme('#09090b', '#fafafa')};
} }
.block.heading-3:empty::before { .block.heading-3:empty::before {
content: "Heading 3"; content: "Heading 3";
color: ${cssManager.bdTheme('#999', '#666')}; color: ${cssManager.bdTheme('#71717a', '#71717a')};
pointer-events: none; pointer-events: none;
font-size: 20px; font-size: 20px;
line-height: 1.4; line-height: 1.4;
@ -134,10 +137,10 @@ export const wysiwygStyles = css`
} }
.block.quote { .block.quote {
border-left: 3px solid ${cssManager.bdTheme('#0066cc', '#4d94ff')}; border-left: 2px solid ${cssManager.bdTheme('#e5e7eb', '#27272a')};
padding-left: 20px; padding-left: 20px;
font-style: italic; font-style: italic;
color: ${cssManager.bdTheme('#555', '#b0b0b0')}; color: ${cssManager.bdTheme('#71717a', '#a1a1aa')};
margin-left: 0; margin-left: 0;
margin-right: 0; margin-right: 0;
line-height: 1.6; line-height: 1.6;
@ -145,7 +148,7 @@ export const wysiwygStyles = css`
.block.quote:empty::before { .block.quote:empty::before {
content: "Quote"; content: "Quote";
color: ${cssManager.bdTheme('#999', '#666')}; color: ${cssManager.bdTheme('#71717a', '#71717a')};
pointer-events: none; pointer-events: none;
font-size: 16px; font-size: 16px;
line-height: 1.6; line-height: 1.6;
@ -162,33 +165,33 @@ export const wysiwygStyles = css`
position: absolute; position: absolute;
top: 0; top: 0;
right: 0; right: 0;
background: ${cssManager.bdTheme('#e1e4e8', '#333333')}; background: ${cssManager.bdTheme('#f4f4f5', '#27272a')};
color: ${cssManager.bdTheme('#586069', '#8b949e')}; color: ${cssManager.bdTheme('#71717a', '#a1a1aa')};
padding: 4px 12px; padding: 4px 12px;
font-size: 12px; font-size: 12px;
border-radius: 0 6px 0 6px; border-radius: 0 4px 0 4px;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
text-transform: lowercase; text-transform: lowercase;
z-index: 1; z-index: 1;
} }
.block.code { .block.code {
background: ${cssManager.bdTheme('#f8f8f8', '#0d0d0d')}; background: ${cssManager.bdTheme('#f4f4f5', '#18181b')};
border: 1px solid ${cssManager.bdTheme('#e0e0e0', '#2a2a2a')}; border: 1px solid ${cssManager.bdTheme('#e5e7eb', '#27272a')};
border-radius: 6px; border-radius: 4px;
padding: 16px 20px; padding: 16px;
padding-top: 32px; /* Make room for language indicator */ padding-top: 32px; /* Make room for language indicator */
font-family: 'SF Mono', 'Monaco', 'Inconsolata', 'Fira Code', monospace; font-family: 'SF Mono', 'Monaco', 'Inconsolata', 'Fira Code', monospace;
font-size: 14px; font-size: 14px;
line-height: 1.5; line-height: 1.5;
white-space: pre-wrap; white-space: pre-wrap;
color: ${cssManager.bdTheme('#24292e', '#e1e4e8')}; color: ${cssManager.bdTheme('#09090b', '#fafafa')};
overflow-x: auto; overflow-x: auto;
} }
.block.code:empty::before { .block.code:empty::before {
content: "// Code block"; content: "// Code block";
color: ${cssManager.bdTheme('#999', '#666')}; color: ${cssManager.bdTheme('#71717a', '#71717a')};
pointer-events: none; pointer-events: none;
font-family: 'SF Mono', 'Monaco', 'Inconsolata', 'Fira Code', monospace; font-family: 'SF Mono', 'Monaco', 'Inconsolata', 'Fira Code', monospace;
font-size: 14px; font-size: 14px;
@ -233,16 +236,16 @@ export const wysiwygStyles = css`
.block.divider hr { .block.divider hr {
border: none; border: none;
border-top: 1px solid ${cssManager.bdTheme('#e0e0e0', '#333')}; border-top: 1px solid ${cssManager.bdTheme('#e5e7eb', '#27272a')};
margin: 0; margin: 0;
} }
.slash-menu { .slash-menu {
position: absolute; position: absolute;
background: ${cssManager.bdTheme('#ffffff', '#262626')}; background: ${cssManager.bdTheme('#ffffff', '#09090b')};
border: 1px solid ${cssManager.bdTheme('#e0e0e0', '#404040')}; border: 1px solid ${cssManager.bdTheme('#e5e7eb', '#27272a')};
border-radius: 8px; border-radius: 4px;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12); box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1), 0 1px 2px rgba(0, 0, 0, 0.06);
padding: 4px; padding: 4px;
z-index: 1000; z-index: 1000;
min-width: 220px; min-width: 220px;
@ -253,21 +256,21 @@ export const wysiwygStyles = css`
} }
.slash-menu-item { .slash-menu-item {
padding: 10px 12px; padding: 8px 10px;
cursor: pointer; cursor: pointer;
transition: all 0.15s ease; transition: all 0.15s ease;
display: flex; display: flex;
align-items: center; align-items: center;
gap: 12px; gap: 12px;
border-radius: 4px; border-radius: 3px;
color: ${cssManager.bdTheme('#000000', '#e0e0e0')}; color: ${cssManager.bdTheme('#09090b', '#fafafa')};
font-size: 14px; font-size: 14px;
} }
.slash-menu-item:hover, .slash-menu-item:hover,
.slash-menu-item.selected { .slash-menu-item.selected {
background: ${cssManager.bdTheme('#f0f0f0', '#333333')}; background: ${cssManager.bdTheme('#f4f4f5', '#27272a')};
color: ${cssManager.bdTheme('#000000', '#ffffff')}; color: ${cssManager.bdTheme('#09090b', '#fafafa')};
} }
.slash-menu-item .icon { .slash-menu-item .icon {
@ -277,23 +280,23 @@ export const wysiwygStyles = css`
align-items: center; align-items: center;
justify-content: center; justify-content: center;
font-size: 16px; font-size: 16px;
color: ${cssManager.bdTheme('#666', '#999')}; color: ${cssManager.bdTheme('#71717a', '#a1a1aa')};
font-weight: 600; font-weight: 600;
} }
.slash-menu-item:hover .icon, .slash-menu-item:hover .icon,
.slash-menu-item.selected .icon { .slash-menu-item.selected .icon {
color: ${cssManager.bdTheme('#0066cc', '#4d94ff')}; color: ${cssManager.bdTheme('#3b82f6', '#3b82f6')};
} }
.toolbar { .toolbar {
position: absolute; position: absolute;
top: -40px; top: -40px;
left: 0; left: 0;
background: ${cssManager.bdTheme('#ffffff', '#262626')}; background: ${cssManager.bdTheme('#ffffff', '#09090b')};
border: 1px solid ${cssManager.bdTheme('#e0e0e0', '#404040')}; border: 1px solid ${cssManager.bdTheme('#e5e7eb', '#27272a')};
border-radius: 6px; border-radius: 4px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1); box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1), 0 1px 2px rgba(0, 0, 0, 0.06);
padding: 4px; padding: 4px;
display: none; display: none;
gap: 4px; gap: 4px;
@ -310,17 +313,17 @@ export const wysiwygStyles = css`
border: none; border: none;
background: transparent; background: transparent;
cursor: pointer; cursor: pointer;
border-radius: 4px; border-radius: 3px;
transition: all 0.15s ease; transition: all 0.15s ease;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
color: ${cssManager.bdTheme('#000000', '#e0e0e0')}; color: ${cssManager.bdTheme('#09090b', '#fafafa')};
} }
.toolbar-button:hover { .toolbar-button:hover {
background: ${cssManager.bdTheme('#f0f0f0', '#333333')}; background: ${cssManager.bdTheme('#f4f4f5', '#27272a')};
color: ${cssManager.bdTheme('#0066cc', '#4d94ff')}; color: ${cssManager.bdTheme('#3b82f6', '#3b82f6')};
} }
/* Drag and Drop Styles */ /* Drag and Drop Styles */
@ -360,7 +363,7 @@ export const wysiwygStyles = css`
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
color: ${cssManager.bdTheme('#999', '#666')}; color: ${cssManager.bdTheme('#71717a', '#71717a')};
border-radius: 4px; border-radius: 4px;
} }
@ -375,13 +378,13 @@ export const wysiwygStyles = css`
} }
.drag-handle:hover { .drag-handle:hover {
color: ${cssManager.bdTheme('#666', '#999')}; color: ${cssManager.bdTheme('#71717a', '#a1a1aa')};
background: ${cssManager.bdTheme('rgba(0, 0, 0, 0.05)', 'rgba(255, 255, 255, 0.05)')}; background: ${cssManager.bdTheme('#f4f4f5', '#27272a')};
} }
.drag-handle:active { .drag-handle:active {
cursor: grabbing; cursor: grabbing;
background: ${cssManager.bdTheme('rgba(0, 0, 0, 0.1)', 'rgba(255, 255, 255, 0.1)')}; background: ${cssManager.bdTheme('#e5e7eb', '#3f3f46')};
} }
.block-wrapper.dragging { .block-wrapper.dragging {
@ -407,9 +410,9 @@ export const wysiwygStyles = css`
position: absolute; position: absolute;
left: 0; left: 0;
right: 0; right: 0;
background: ${cssManager.bdTheme('rgba(0, 102, 204, 0.08)', 'rgba(77, 148, 255, 0.08)')}; background: ${cssManager.bdTheme('rgba(59, 130, 246, 0.05)', 'rgba(59, 130, 246, 0.05)')};
border: 2px dashed ${cssManager.bdTheme('#0066cc', '#4d94ff')}; border: 2px dashed ${cssManager.bdTheme('#3b82f6', '#3b82f6')};
border-radius: 8px; border-radius: 4px;
transition: top 0.2s ease, height 0.2s ease; transition: top 0.2s ease, height 0.2s ease;
pointer-events: none; pointer-events: none;
z-index: 1999; z-index: 1999;
@ -426,50 +429,21 @@ export const wysiwygStyles = css`
user-select: none; user-select: none;
} }
/* Block Settings Button */ /* Block Settings Button - Removed in favor of context menu */
.block-settings {
position: absolute;
right: -40px;
top: 50%;
transform: translateY(-50%);
width: 28px;
height: 28px;
cursor: pointer;
opacity: 0;
transition: opacity 0.2s ease;
display: flex;
align-items: center;
justify-content: center;
color: ${cssManager.bdTheme('#999', '#666')};
border-radius: 4px;
}
.block-wrapper:hover .block-settings {
opacity: 1;
}
.block-settings:hover {
color: ${cssManager.bdTheme('#666', '#999')};
background: ${cssManager.bdTheme('rgba(0, 0, 0, 0.05)', 'rgba(255, 255, 255, 0.05)')};
}
.block-settings:active {
background: ${cssManager.bdTheme('rgba(0, 0, 0, 0.1)', 'rgba(255, 255, 255, 0.1)')};
}
/* Text Selection Styles */ /* Text Selection Styles */
.block ::selection { .block ::selection {
background: ${cssManager.bdTheme('rgba(0, 102, 204, 0.3)', 'rgba(77, 148, 255, 0.3)')}; background: ${cssManager.bdTheme('rgba(59, 130, 246, 0.2)', 'rgba(59, 130, 246, 0.2)')};
color: inherit; color: inherit;
} }
/* Formatting Menu */ /* Formatting Menu */
.formatting-menu { .formatting-menu {
position: absolute; position: absolute;
background: ${cssManager.bdTheme('#ffffff', '#262626')}; background: ${cssManager.bdTheme('#ffffff', '#09090b')};
border: 1px solid ${cssManager.bdTheme('#e0e0e0', '#404040')}; border: 1px solid ${cssManager.bdTheme('#e5e7eb', '#27272a')};
border-radius: 6px; border-radius: 4px;
box-shadow: 0 2px 16px rgba(0, 0, 0, 0.15); box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1), 0 1px 2px rgba(0, 0, 0, 0.06);
padding: 4px; padding: 4px;
display: flex; display: flex;
gap: 2px; gap: 2px;
@ -480,7 +454,7 @@ export const wysiwygStyles = css`
@keyframes fadeInScale { @keyframes fadeInScale {
from { from {
opacity: 0; opacity: 0;
transform: scale(0.95) translateY(5px); transform: scale(0.98) translateY(2px);
} }
to { to {
opacity: 1; opacity: 1;
@ -494,20 +468,20 @@ export const wysiwygStyles = css`
border: none; border: none;
background: transparent; background: transparent;
cursor: pointer; cursor: pointer;
border-radius: 4px; border-radius: 3px;
transition: all 0.15s ease; transition: all 0.15s ease;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
color: ${cssManager.bdTheme('#000000', '#e0e0e0')}; color: ${cssManager.bdTheme('#09090b', '#fafafa')};
font-weight: 600; font-weight: 600;
font-size: 14px; font-size: 14px;
position: relative; position: relative;
} }
.format-button:hover { .format-button:hover {
background: ${cssManager.bdTheme('#f0f0f0', '#333333')}; background: ${cssManager.bdTheme('#f4f4f5', '#27272a')};
color: ${cssManager.bdTheme('#0066cc', '#4d94ff')}; color: ${cssManager.bdTheme('#3b82f6', '#3b82f6')};
} }
.format-button:active { .format-button:active {
@ -535,7 +509,7 @@ export const wysiwygStyles = css`
.block strong, .block strong,
.block b { .block b {
font-weight: 600; font-weight: 600;
color: ${cssManager.bdTheme('#000000', '#ffffff')}; color: ${cssManager.bdTheme('#09090b', '#fafafa')};
} }
.block em, .block em,
@ -554,22 +528,22 @@ export const wysiwygStyles = css`
} }
.block code { .block code {
background: ${cssManager.bdTheme('rgba(0, 0, 0, 0.06)', 'rgba(255, 255, 255, 0.1)')}; background: ${cssManager.bdTheme('#f4f4f5', '#27272a')};
padding: 2px 6px; padding: 2px 6px;
border-radius: 3px; border-radius: 3px;
font-family: 'SF Mono', 'Monaco', 'Inconsolata', 'Fira Code', monospace; font-family: 'SF Mono', 'Monaco', 'Inconsolata', 'Fira Code', monospace;
font-size: 0.9em; font-size: 0.9em;
color: ${cssManager.bdTheme('#d14', '#ff6b6b')}; color: ${cssManager.bdTheme('#09090b', '#fafafa')};
} }
.block a { .block a {
color: ${cssManager.bdTheme('#0066cc', '#4d94ff')}; color: ${cssManager.bdTheme('#3b82f6', '#3b82f6')};
text-decoration: none; text-decoration: none;
border-bottom: 1px solid transparent; border-bottom: 1px solid transparent;
transition: border-color 0.15s ease; transition: border-color 0.15s ease;
} }
.block a:hover { .block a:hover {
border-bottom-color: ${cssManager.bdTheme('#0066cc', '#4d94ff')}; border-bottom-color: ${cssManager.bdTheme('#3b82f6', '#3b82f6')};
} }
`; `;