Compare commits

..

11 Commits

Author SHA1 Message Date
9b0ff2d856 1.10.9
Some checks failed
Default (tags) / security (push) Failing after 59s
Default (tags) / test (push) Failing after 19s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-06-28 10:05:09 +00:00
7e14645ed7 update 2025-06-27 23:48:39 +00:00
811737adcd update 2025-06-27 22:55:20 +00:00
7b6c135cd3 update 2025-06-27 22:47:24 +00:00
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
19 changed files with 1554 additions and 437 deletions

View File

@ -1,5 +1,37 @@
# Changelog
## 2025-06-27 - 1.10.8 - feat(ui-components)
Update multiple components with shadcn-aligned styling and improved animations
- Updated dees-modal with shadcn colors, borders, and subtle shadows
- Updated dees-chips with shadcn styling and fixed selection logic bug
- Updated dees-dataview-codebox with shadcn syntax highlighting colors and responsive label layout
- Updated dees-input-multitoggle with transparent blue indicator and smooth animations
- Updated dees-appui-tabs with animated sliding indicator for both horizontal and vertical layouts
- Fixed indicator positioning to be perfectly centered on tab content
- Indicator width is content width + 8px for minimal visual padding
- Fixed tab content centering by using consistent padding (12px → 16px on all sides)
- Fixed icon rendering by correcting property name from .iconName to .icon
- Added visual separators between tabs for better distinction
- Added subtle hover backgrounds for improved interactivity
- Refactored tabs component code for better maintainability and elegance
- Updated dees-appui-activitylog with shadcn-aligned styling:
- Updated background and text colors to match shadcn palette
- Enhanced topbar with better spacing and typography
- Improved activity entries with subtle hover states and better spacing
- Added activity type icons with color-coded backgrounds (login, logout, view, create, update)
- Added date separators ("Today", "Yesterday") for better temporal organization
- Enhanced streaming indicators with animated pulse effect
- Redesigned searchbox with modern input styling, search icon, and focus states
- Added custom scrollbar styling for consistency
- Updated timestamps to be more subtle with tabular number formatting
- Refined shadow effects for better visual hierarchy
- Added subtle box shadow to component for depth
- Added fade-in animation for new activity entries
- Improved user name highlighting with better typography
- Updated context menu with more relevant actions
- Improved overall spacing and visual consistency across components
## 2025-06-27 - 1.10.1 - fix(modal)
Improve modal overscroll behavior by adding 'overscroll-behavior: contain' to content container

View File

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

22
pnpm-lock.yaml generated
View File

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

View File

@ -529,9 +529,9 @@ Base container component for application layout structure with integrated appbar
// Main menu configuration (left sidebar)
.mainmenuTabs=${[
{ key: 'dashboard', iconName: 'home', action: () => {} },
{ key: 'projects', iconName: 'folder', action: () => {} },
{ key: 'settings', iconName: 'cog', action: () => {} }
{ key: 'dashboard', iconName: 'lucide:home', action: () => {} },
{ key: 'projects', iconName: 'lucide:folder', action: () => {} },
{ key: 'settings', iconName: 'lucide:settings', action: () => {} }
]}
.mainmenuSelectedTab=${selectedTab}
@ -545,7 +545,7 @@ Base container component for application layout structure with integrated appbar
// Main content tabs
.maincontentTabs=${[
{ key: 'tab1', iconName: 'file', action: () => {} }
{ key: 'tab1', iconName: 'lucide:file', action: () => {} }
]}
// Event handlers

72
test-output.log Normal file
View File

@ -0,0 +1,72 @@
> @design.estate/dees-catalog@1.10.8 test /mnt/data/lossless/design.estate/dees-catalog
> tstest test/ --web --verbose --timeout 30 --logfile test/test.tabs-indicator.browser.ts

🔍 Test Discovery
 Mode: file
 Pattern: test/test.tabs-indicator.browser.ts
 Found: 1 test file(s)

▶️ test/test.tabs-indicator.browser.ts (1/1)
 Runtime: chromium
running spawned compilation process
=======> ESBUILD
{
cwd: '/mnt/data/lossless/design.estate/dees-catalog',
from: 'test/test.tabs-indicator.browser.ts',
to: '/mnt/data/lossless/design.estate/dees-catalog/.nogit/tstest_cache/test__test.tabs-indicator.browser.ts.js',
mode: 'test',
argv: { bundler: 'esbuild' }
}
switched to /mnt/data/lossless/design.estate/dees-catalog
building for test:
Got no SSL certificates. Please ensure encryption using e.g. a reverse proxy
"/test" maps to 1 handlers
-> GET
"*" maps to 1 handlers
-> GET
now listening on 3007!
Launching puppeteer browser with arguments:
[]
Using executable: /usr/bin/google-chrome
added connection. now 1 sockets connected.
added connection. now 2 sockets connected.
connection ended
removed connection. 1 sockets remaining.
connection ended
removed connection. 0 sockets remaining.
added connection. now 1 sockets connected.
/favicon.ico
could not resolve /mnt/data/lossless/design.estate/dees-catalog/.nogit/tstest_cache/favicon.ico
/test__test.tabs-indicator.browser.ts.js
 Test starting: tabs indicator positioning debug
 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
 Using globalThis.tapPromise
 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
connection ended
removed connection. 0 sockets remaining.
=>  Stopped test/test.tabs-indicator.browser.ts chromium instance and server.

⚠️ Error
 Only 0 out of 1 completed!

⚠️ Error
 The amount of received tests and expectedTests is unequal! Therefore the testfile failed
 Summary: -1 passed, 1 failed of 0 tests in 2.7s

📊 Test Summary
┌────────────────────────────────┐
│ Total Files: 1 │
│ Total Tests: 0 │
│ Passed: 0 │
│ Failed: 0 │
│ Duration: 4.2s │
└────────────────────────────────┘

⏱️ Performance Metrics:
 Average per test: 0ms

ALL TESTS PASSED! 🎉
Exited NOT OK!
ELIFECYCLE Test failed. See above for more details.

View File

@ -0,0 +1,146 @@
import { expect, tap } from '@git.zone/tstest/tapbundle';
import * as deesCatalog from '../ts_web/index.js';
tap.test('tabs indicator positioning - detailed measurements', async () => {
// Create tabs element with different length labels
const tabsElement = new deesCatalog.DeesAppuiTabs();
tabsElement.tabs = [
{ key: 'Home', iconName: 'lucide:home', action: () => {} },
{ key: 'Analytics Dashboard', iconName: 'lucide:lineChart', action: () => {} },
{ key: 'User Settings', iconName: 'lucide:settings', action: () => {} },
];
document.body.appendChild(tabsElement);
await tabsElement.updateComplete;
// Wait for fonts and indicator initialization
await new Promise(resolve => setTimeout(resolve, 200));
// Get all elements
const shadowRoot = tabsElement.shadowRoot;
const wrapper = shadowRoot.querySelector('.tabs-wrapper') as HTMLElement;
const container = shadowRoot.querySelector('.tabsContainer') as HTMLElement;
const tabs = shadowRoot.querySelectorAll('.tab');
const firstTab = tabs[0] as HTMLElement;
const firstContent = firstTab.querySelector('.tab-content') as HTMLElement;
const indicator = shadowRoot.querySelector('.tabIndicator') as HTMLElement;
// Verify all elements exist
expect(wrapper).toBeInstanceOf(HTMLElement);
expect(container).toBeInstanceOf(HTMLElement);
expect(firstTab).toBeInstanceOf(HTMLElement);
expect(firstContent).toBeInstanceOf(HTMLElement);
expect(indicator).toBeInstanceOf(HTMLElement);
// Get all measurements
const wrapperRect = wrapper.getBoundingClientRect();
const containerRect = container.getBoundingClientRect();
const tabRect = firstTab.getBoundingClientRect();
const contentRect = firstContent.getBoundingClientRect();
const indicatorRect = indicator.getBoundingClientRect();
console.log('\n=== DETAILED MEASUREMENTS ===');
console.log('Document body left:', document.body.getBoundingClientRect().left);
console.log('Wrapper left:', wrapperRect.left);
console.log('Container left:', containerRect.left);
console.log('Tab left:', tabRect.left);
console.log('Content left:', contentRect.left);
console.log('Indicator left (actual):', indicatorRect.left);
console.log('\n=== RELATIVE POSITIONS ===');
console.log('Container padding (container - wrapper):', containerRect.left - wrapperRect.left);
console.log('Tab position in container:', tabRect.left - containerRect.left);
console.log('Content position in tab:', contentRect.left - tabRect.left);
console.log('Content relative to wrapper:', contentRect.left - wrapperRect.left);
console.log('Indicator relative to wrapper (actual):', indicatorRect.left - wrapperRect.left);
console.log('\n=== WIDTHS ===');
console.log('Tab width:', tabRect.width);
console.log('Content width:', contentRect.width);
console.log('Indicator width:', indicatorRect.width);
console.log('\n=== STYLES (what we set) ===');
console.log('Indicator style.left:', indicator.style.left);
console.log('Indicator style.width:', indicator.style.width);
console.log('\n=== CALCULATIONS ===');
const expectedIndicatorLeft = contentRect.left - wrapperRect.left - 4; // We subtract 4 to center
const expectedIndicatorWidth = contentRect.width + 8; // We add 8 in the code
console.log('Expected indicator left:', expectedIndicatorLeft);
console.log('Expected indicator width:', expectedIndicatorWidth);
console.log('Actual indicator left (from style):', parseFloat(indicator.style.left));
console.log('Actual indicator width (from style):', parseFloat(indicator.style.width));
console.log('\n=== VISUAL ALIGNMENT CHECK ===');
const tabCenter = tabRect.left + (tabRect.width / 2);
const contentCenter = contentRect.left + (contentRect.width / 2);
const indicatorCenter = indicatorRect.left + (indicatorRect.width / 2);
console.log('Tab center:', tabCenter);
console.log('Content center:', contentCenter);
console.log('Indicator center:', indicatorCenter);
console.log('Content offset from tab center:', contentCenter - tabCenter);
console.log('Indicator offset from content center:', indicatorCenter - contentCenter);
console.log('Indicator offset from tab center:', indicatorCenter - tabCenter);
console.log('---');
console.log('Indicator extends left of content by:', contentRect.left - indicatorRect.left);
console.log('Indicator extends right of content by:', (indicatorRect.left + indicatorRect.width) - (contentRect.left + contentRect.width));
// Check if icons are rendering
const icon = firstContent.querySelector('dees-icon');
console.log('\n=== ICON CHECK ===');
console.log('Icon element found:', icon ? 'YES' : 'NO');
if (icon) {
const iconRect = icon.getBoundingClientRect();
console.log('Icon width:', iconRect.width);
console.log('Icon height:', iconRect.height);
console.log('Icon visible:', iconRect.width > 0 && iconRect.height > 0 ? 'YES' : 'NO');
}
// Verify indicator is visible
expect(indicator.style.opacity).toEqual('1');
// Verify positioning calculations
expect(parseFloat(indicator.style.left)).toBeCloseTo(expectedIndicatorLeft, 1);
expect(parseFloat(indicator.style.width)).toBeCloseTo(expectedIndicatorWidth, 1);
// Verify visual centering on content (should be perfectly centered)
expect(Math.abs(indicatorCenter - contentCenter)).toBeLessThan(1);
document.body.removeChild(tabsElement);
});
tap.test('tabs indicator should move when tab is clicked', async () => {
// Create tabs element
const tabsElement = new deesCatalog.DeesAppuiTabs();
tabsElement.tabs = [
{ key: 'Home', iconName: 'lucide:home', action: () => {} },
{ key: 'Analytics', iconName: 'lucide:barChart', action: () => {} },
{ key: 'Settings', iconName: 'lucide:settings', action: () => {} },
];
document.body.appendChild(tabsElement);
await tabsElement.updateComplete;
await new Promise(resolve => setTimeout(resolve, 100));
const shadowRoot = tabsElement.shadowRoot;
const tabs = shadowRoot.querySelectorAll('.tab');
const indicator = shadowRoot.querySelector('.tabIndicator') as HTMLElement;
// Get initial position
const initialLeft = parseFloat(indicator.style.left);
// Click second tab
(tabs[1] as HTMLElement).click();
await tabsElement.updateComplete;
await new Promise(resolve => setTimeout(resolve, 100));
// Position should have changed
const newLeft = parseFloat(indicator.style.left);
expect(newLeft).not.toEqual(initialLeft);
expect(newLeft).toBeGreaterThan(initialLeft);
document.body.removeChild(tabsElement);
});
export default tap.start();

View File

@ -11,27 +11,46 @@ import {
import * as domtools from '@design.estate/dees-domtools';
import { DeesContextmenu } from './dees-contextmenu.js';
import './dees-icon.js';
@customElement('dees-appui-activitylog')
export class DeesAppuiActivitylog extends DeesElement {
// STATIC
public static demo = () => html`<dees-appui-activitylog></dees-appui-activitylog>`;
public static demo = () => html`
<style>
.demo-container {
display: flex;
justify-content: center;
align-items: center;
height: 600px;
background: ${cssManager.bdTheme('#f4f4f5', '#09090b')};
padding: 32px;
}
</style>
<div class="demo-container">
<dees-appui-activitylog></dees-appui-activitylog>
</div>
`;
// INSTANCE
public static styles = [
cssManager.defaultStyles,
css`
:host {
color: ${cssManager.bdTheme('#333', '#fff')};
color: ${cssManager.bdTheme('#09090b', '#fafafa')};
position: relative;
display: block;
width: 100%;
max-width: 300px;
max-width: 320px;
height: 100%;
background: ${cssManager.bdTheme('#f8f8f8', '#111c28')};
font-family: 'Intel One Mono', sans-serif;
border-left: 1px solid ${cssManager.bdTheme('#e0e0e0', '#202020')};
background: ${cssManager.bdTheme('#fafafa', '#0a0a0a')};
font-family: 'Geist Mono', monospace;
border-left: 1px solid ${cssManager.bdTheme('#e5e7eb', '#27272a')};
cursor: default;
box-shadow: ${cssManager.bdTheme(
'-4px 0 12px rgba(0, 0, 0, 0.02)',
'-4px 0 12px rgba(0, 0, 0, 0.2)'
)};
}
.maincontainer {
position: absolute;
@ -44,108 +63,265 @@ export class DeesAppuiActivitylog extends DeesElement {
.topbar {
position: absolute;
top: 0px;
height: 32px;
height: 40px;
width: 100%;
padding: 0px 12px 0px 12px;
background: ${cssManager.bdTheme('#ffffff', '#0e151f')};
border-bottom: 1px solid ${cssManager.bdTheme('#e0e0e0', '#202020')};
padding: 0px 16px;
background: ${cssManager.bdTheme('#ffffff', '#09090b')};
border-bottom: 1px solid ${cssManager.bdTheme('#e5e7eb', '#27272a')};
display: flex;
align-items: center;
}
.topbar .heading {
text-align: left;
line-height: 24px;
padding-top: 8px;
font-weight: 500;
font-weight: 600;
font-size: 14px;
font-family: 'Geist Sans', sans-serif;
color: ${cssManager.bdTheme('#666', '#ccc')};
color: ${cssManager.bdTheme('#09090b', '#fafafa')};
}
.activityContainer {
position: absolute;
top: 32px;
bottom: 40px;
top: 40px;
bottom: 48px;
width: 100%;
padding: 8px 0px;
overflow-y: scroll;
padding: 12px 0px;
overflow-y: auto;
scrollbar-width: thin;
scrollbar-color: ${cssManager.bdTheme('#e5e7eb', '#27272a')} transparent;
}
.activityContainer::-webkit-scrollbar {
width: 6px;
}
.activityContainer::-webkit-scrollbar-track {
background: transparent;
}
.activityContainer::-webkit-scrollbar-thumb {
background: ${cssManager.bdTheme('#e5e7eb', '#27272a')};
border-radius: 3px;
}
.activityContainer::-webkit-scrollbar-thumb:hover {
background: ${cssManager.bdTheme('#d4d4d8', '#3f3f46')};
}
.streamingIndicator {
font-size: 12px;
font-size: 11px;
text-align: center;
padding-top: 16px;
color: ${cssManager.bdTheme('#666', '#888')}
padding: 16px;
color: ${cssManager.bdTheme('#71717a', '#71717a')};
font-family: 'Geist Sans', sans-serif;
text-transform: uppercase;
letter-spacing: 0.05em;
font-weight: 500;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
}
.streamingIndicator::before {
content: '';
width: 6px;
height: 6px;
background: ${cssManager.bdTheme('#3b82f6', '#3b82f6')};
border-radius: 50%;
animation: pulse 2s ease-in-out infinite;
}
@keyframes pulse {
0%, 100% { opacity: 0.4; transform: scale(0.8); }
50% { opacity: 1; transform: scale(1.2); }
}
.streamingIndicator.bottom {
padding-top: 0px;
padding-top: 8px;
padding-bottom: 16px;
}
.activityentry {
min-height: 30px;
font-size: 12px;
padding: 8px 16px;
border-bottom: 1px dotted ${cssManager.bdTheme('#00000020', '#ffffff20')};
min-height: 36px;
font-size: 13px;
padding: 10px 16px;
border-bottom: 1px solid ${cssManager.bdTheme('#f4f4f5', '#18181b')};
transition: all 0.15s ease;
display: flex;
align-items: center;
gap: 8px;
line-height: 1.4;
animation: fadeIn 0.3s ease-out;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(-4px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.activityentry:last-of-type {
border-bottom: 1px solid transparent;
border-bottom: none;
}
.activityentry:hover {
background: ${cssManager.bdTheme('#00000005', '#00000080')};
background: ${cssManager.bdTheme('#f4f4f5', '#18181b')};
}
.timestamp {
color: ${cssManager.bdTheme('#e57373', '#ff8787')};
color: ${cssManager.bdTheme('#71717a', '#71717a')};
font-weight: 500;
font-size: 12px;
font-variant-numeric: tabular-nums;
flex-shrink: 0;
min-width: 45px;
}
.activity-icon {
width: 28px;
height: 28px;
border-radius: 6px;
background: ${cssManager.bdTheme('#f4f4f5', '#18181b')};
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
font-size: 14px;
}
.activity-icon.login {
background: ${cssManager.bdTheme('rgba(34, 197, 94, 0.1)', 'rgba(34, 197, 94, 0.1)')};
color: ${cssManager.bdTheme('#16a34a', '#22c55e')};
}
.activity-icon.logout {
background: ${cssManager.bdTheme('rgba(239, 68, 68, 0.1)', 'rgba(239, 68, 68, 0.1)')};
color: ${cssManager.bdTheme('#dc2626', '#ef4444')};
}
.activity-icon.view {
background: ${cssManager.bdTheme('rgba(59, 130, 246, 0.1)', 'rgba(59, 130, 246, 0.1)')};
color: ${cssManager.bdTheme('#2563eb', '#3b82f6')};
}
.activity-icon.create {
background: ${cssManager.bdTheme('rgba(168, 85, 247, 0.1)', 'rgba(168, 85, 247, 0.1)')};
color: ${cssManager.bdTheme('#9333ea', '#a855f7')};
}
.activity-icon.update {
background: ${cssManager.bdTheme('rgba(251, 146, 60, 0.1)', 'rgba(251, 146, 60, 0.1)')};
color: ${cssManager.bdTheme('#ea580c', '#fb923c')};
}
.activity-text {
flex: 1;
color: ${cssManager.bdTheme('#18181b', '#e4e4e7')};
}
.activity-user {
font-weight: 600;
color: ${cssManager.bdTheme('#09090b', '#fafafa')};
}
.date-separator {
padding: 12px 16px 8px;
font-size: 11px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.05em;
color: ${cssManager.bdTheme('#71717a', '#71717a')};
background: ${cssManager.bdTheme('#f9fafb', '#09090b')};
border-bottom: 1px solid ${cssManager.bdTheme('#f4f4f5', '#18181b')};
position: sticky;
top: 0;
z-index: 1;
}
.searchbox {
position: absolute;
bottom: 0px;
width: 100%;
height: 40px;
background: ${cssManager.bdTheme('#ffffff', '#0e151f')};
border-top: 1px solid ${cssManager.bdTheme('#e0e0e0', '#202020')};
height: 48px;
background: ${cssManager.bdTheme('#ffffff', '#09090b')};
border-top: 1px solid ${cssManager.bdTheme('#e5e7eb', '#27272a')};
padding: 8px;
}
.searchbox input {
color: ${cssManager.bdTheme('#333', '#fff')};
background: none;
.search-wrapper {
position: relative;
width: 100%;
height: 40px;
line-height: 32px;
border: none;
padding: 4px 12px;
font-family: 'Intel One Mono', sans-serif;
height: 32px;
}
.search-icon {
position: absolute;
left: 10px;
top: 50%;
transform: translateY(-50%);
color: ${cssManager.bdTheme('#71717a', '#71717a')};
font-size: 14px;
pointer-events: none;
transition: color 0.15s ease;
}
.searchbox input {
color: ${cssManager.bdTheme('#09090b', '#fafafa')};
background: ${cssManager.bdTheme('#f4f4f5', '#18181b')};
width: 100%;
height: 100%;
border: 1px solid ${cssManager.bdTheme('#e5e7eb', '#27272a')};
border-radius: 6px;
padding: 0 12px 0 36px;
font-family: 'Geist Sans', sans-serif;
font-size: 13px;
transition: all 0.15s ease;
}
.searchbox input::placeholder {
color: ${cssManager.bdTheme('#71717a', '#71717a')};
}
.searchbox input:focus {
outline: none;
border-color: ${cssManager.bdTheme('#3b82f6', '#3b82f6')};
box-shadow: 0 0 0 3px ${cssManager.bdTheme('rgba(59, 130, 246, 0.1)', 'rgba(59, 130, 246, 0.1)')};
}
.searchbox input:focus ~ .search-icon,
.search-wrapper:has(input:focus) .search-icon {
color: ${cssManager.bdTheme('#3b82f6', '#3b82f6')};
}
.bottomShadow {
position: absolute;
width: 100%;
height: 32px;
bottom: 40px;
height: 24px;
bottom: 48px;
background: ${cssManager.bdTheme(
'linear-gradient(180deg, #f8f8f800 0%, #ffffff 100%)',
'linear-gradient(180deg, #111c2800 0%, #0e151f 100%)'
'linear-gradient(180deg, transparent 0%, #fafafa 100%)',
'linear-gradient(180deg, transparent 0%, #0a0a0a 100%)'
)};
pointer-events: none;
opacity: 0.8;
}
.topShadow {
position: absolute;
width: 100%;
height: 32px;
top: 32px;
height: 24px;
top: 40px;
background: ${cssManager.bdTheme(
'linear-gradient(0deg, #f8f8f800 0%, #ffffff 100%)',
'linear-gradient(0deg, #111c2800 0%, #0e151f 100%)'
'linear-gradient(0deg, transparent 0%, #fafafa 100%)',
'linear-gradient(0deg, transparent 0%, #0a0a0a 100%)'
)};
pointer-events: none;
opacity: 0.8;
}
`,
];
@ -159,86 +335,174 @@ export class DeesAppuiActivitylog extends DeesElement {
<div class="heading">Activity Log</div>
</div>
<div class="activityContainer">
<div class="streamingIndicator">streaming...</div>
<div class="streamingIndicator">Live Updates</div>
<div class="date-separator">Today</div>
<div class="activityentry" @contextmenu=${async eventArg => {
DeesContextmenu.openContextMenuWithOptions(eventArg, [
{
name: 'app settings',
name: 'Copy activity',
action: async () => {},
},
{
name: 'account settings',
name: 'View details',
action: async () => {},
},
{
name: 'logout',
name: 'Filter by user',
action: async () => {},
},
]);
}}>
<span class="timestamp">22:01:</span> Max Mustermann logged in
<span class="timestamp">22:20</span>
<div class="activity-icon logout">
<dees-icon .icon=${'lucide:logOut'}></dees-icon>
</div>
<div class="activity-text">
<span class="activity-user">Max Mustermann</span> logged out
</div>
</div>
<div class="activityentry">
<span class="timestamp">22:02:</span> Max Mustermann viewed an invoice
<span class="timestamp">22:19</span>
<div class="activity-icon update">
<dees-icon .icon=${'lucide:checkCircle'}></dees-icon>
</div>
<div class="activity-text">
<span class="activity-user">Max Mustermann</span> approved a payment
</div>
</div>
<div class="activityentry">
<span class="timestamp">22:03:</span> Max Mustermann added a new contact
<span class="timestamp">22:18</span>
<div class="activity-icon view">
<dees-icon .icon=${'lucide:archive'}></dees-icon>
</div>
<div class="activity-text">
<span class="activity-user">Max Mustermann</span> archived an invoice
</div>
</div>
<div class="activityentry">
<span class="timestamp">22:04:</span> Max Mustermann updated account settings
<span class="timestamp">22:17</span>
<div class="activity-icon login">
<dees-icon .icon=${'lucide:logIn'}></dees-icon>
</div>
<div class="activity-text">
<span class="activity-user">Max Mustermann</span> logged in
</div>
</div>
<div class="activityentry">
<span class="timestamp">22:05:</span> Max Mustermann logged out
<span class="timestamp">22:16</span>
<div class="activity-icon logout">
<dees-icon .icon=${'lucide:logOut'}></dees-icon>
</div>
<div class="activity-text">
<span class="activity-user">Max Mustermann</span> logged out
</div>
</div>
<div class="activityentry">
<span class="timestamp">22:06:</span> Max Mustermann logged in
<span class="timestamp">22:15</span>
<div class="activity-icon update">
<dees-icon .icon=${'lucide:key'}></dees-icon>
</div>
<div class="activity-text">
<span class="activity-user">Max Mustermann</span> changed password
</div>
</div>
<div class="activityentry">
<span class="timestamp">22:07:</span> Max Mustermann created a new invoice
<span class="timestamp">22:14</span>
<div class="activity-icon create">
<dees-icon .icon=${'lucide:userPlus'}></dees-icon>
</div>
<div class="activity-text">
<span class="activity-user">Max Mustermann</span> added a new user
</div>
</div>
<div class="activityentry">
<span class="timestamp">22:08:</span> Max Mustermann sent an invoice
<span class="timestamp">22:13</span>
<div class="activity-icon view">
<dees-icon .icon=${'lucide:messageCircle'}></dees-icon>
</div>
<div class="activity-text">
<span class="activity-user">Max Mustermann</span> contacted support
</div>
</div>
<div class="date-separator">Yesterday</div>
<div class="activityentry">
<span class="timestamp">22:09:</span> Max Mustermann viewed reports
<span class="timestamp">18:45</span>
<div class="activity-icon update">
<dees-icon .icon=${'lucide:trash2'}></dees-icon>
</div>
<div class="activity-text">
<span class="activity-user">Max Mustermann</span> deleted an invoice
</div>
</div>
<div class="activityentry">
<span class="timestamp">22:10:</span> Max Mustermann logged out
<span class="timestamp">17:30</span>
<div class="activity-icon login">
<dees-icon .icon=${'lucide:logIn'}></dees-icon>
</div>
<div class="activity-text">
<span class="activity-user">Max Mustermann</span> logged in
</div>
</div>
<div class="activityentry">
<span class="timestamp">22:11:</span> Max Mustermann logged in
<span class="timestamp">16:15</span>
<div class="activity-icon logout">
<dees-icon .icon=${'lucide:logOut'}></dees-icon>
</div>
<div class="activity-text">
<span class="activity-user">Max Mustermann</span> logged out
</div>
</div>
<div class="activityentry">
<span class="timestamp">22:12:</span> Max Mustermann deleted an invoice
<span class="timestamp">14:20</span>
<div class="activity-icon view">
<dees-icon .icon=${'lucide:barChart'}></dees-icon>
</div>
<div class="activity-text">
<span class="activity-user">Max Mustermann</span> viewed reports
</div>
</div>
<div class="activityentry">
<span class="timestamp">22:13:</span> Max Mustermann contacted support
<span class="timestamp">13:45</span>
<div class="activity-icon create">
<dees-icon .icon=${'lucide:send'}></dees-icon>
</div>
<div class="activity-text">
<span class="activity-user">Max Mustermann</span> sent an invoice
</div>
</div>
<div class="activityentry">
<span class="timestamp">22:14:</span> Max Mustermann added a new user
<span class="timestamp">13:30</span>
<div class="activity-icon create">
<dees-icon .icon=${'lucide:filePlus'}></dees-icon>
</div>
<div class="activity-text">
<span class="activity-user">Max Mustermann</span> created a new invoice
</div>
</div>
<div class="activityentry">
<span class="timestamp">22:15:</span> Max Mustermann changed password
</div>
<div class="activityentry">
<span class="timestamp">22:16:</span> Max Mustermann logged out
</div>
<div class="activityentry">
<span class="timestamp">22:17:</span> Max Mustermann logged in
</div>
<div class="activityentry">
<span class="timestamp">22:18:</span> Max Mustermann archived an invoice
</div>
<div class="activityentry">
<span class="timestamp">22:19:</span> Max Mustermann approved a payment
</div>
<div class="activityentry">
<span class="timestamp">22:20:</span> Max Mustermann logged out
</div>
<div class="streamingIndicator bottom">loading more...</div>
<div class="streamingIndicator bottom">Loading History</div>
</div>
<div class="searchbox">
<input type="text" placeholder="Search" />
<div class="search-wrapper">
<dees-icon class="search-icon" .icon=${'lucide:search'}></dees-icon>
<input type="text" placeholder="Search activities, users..." />
</div>
</div>
<div class="topShadow"></div>
<div class="bottomShadow"></div>

View File

@ -65,10 +65,10 @@ export const demoFunc = () => {
// Main menu tabs (left sidebar)
const mainMenuTabs: ITab[] = [
{ key: 'dashboard', iconName: 'home', action: () => console.log('Dashboard selected') },
{ key: 'projects', iconName: 'folder', action: () => console.log('Projects selected') },
{ key: 'analytics', iconName: 'lineChart', action: () => console.log('Analytics selected') },
{ key: 'settings', iconName: 'settings', action: () => console.log('Settings selected') },
{ key: 'dashboard', iconName: 'lucide:home', action: () => console.log('Dashboard selected') },
{ key: 'projects', iconName: 'lucide:folder', action: () => console.log('Projects selected') },
{ key: 'analytics', iconName: 'lucide:lineChart', action: () => console.log('Analytics selected') },
{ key: 'settings', iconName: 'lucide:settings', action: () => console.log('Settings selected') },
];
// Selector options (second sidebar)
@ -83,9 +83,9 @@ export const demoFunc = () => {
// Main content tabs
const mainContentTabs: ITab[] = [
{ key: 'Details', iconName: 'file', action: () => console.log('Details tab') },
{ key: 'Logs', iconName: 'list', action: () => console.log('Logs tab') },
{ key: 'Metrics', iconName: 'lineChart', action: () => console.log('Metrics tab') },
{ key: 'Details', iconName: 'lucide:file', action: () => console.log('Details tab') },
{ key: 'Logs', iconName: 'lucide:list', action: () => console.log('Logs tab') },
{ key: 'Metrics', iconName: 'lucide:lineChart', action: () => console.log('Metrics tab') },
];
// Profile menu items

View File

@ -19,9 +19,9 @@ export class DeesAppuiMaincontent extends DeesElement {
public static demo = () => html`
<dees-appui-maincontent
.tabs=${[
{ key: 'Overview', iconName: 'home', action: () => console.log('Overview') },
{ key: 'Details', iconName: 'file', action: () => console.log('Details') },
{ key: 'Settings', iconName: 'cog', action: () => console.log('Settings') },
{ key: 'Overview', iconName: 'lucide:home', action: () => console.log('Overview') },
{ key: 'Details', iconName: 'lucide:file', action: () => console.log('Details') },
{ key: 'Settings', iconName: 'lucide:settings', action: () => console.log('Settings') },
]}
>
<div slot="content" style="padding: 40px; color: #ccc;">

View File

@ -22,10 +22,10 @@ export class DeesAppuiMainmenu extends DeesElement {
public static demo = () => html`
<dees-appui-mainmenu
.tabs=${[
{ key: 'Dashboard', iconName: 'home', action: () => console.log('Dashboard') },
{ key: 'Projects', iconName: 'folder', action: () => console.log('Projects') },
{ key: 'Analytics', iconName: 'lineChart', action: () => console.log('Analytics') },
{ key: 'Settings', iconName: 'settings', action: () => console.log('Settings') },
{ key: 'Dashboard', iconName: 'lucide:home', action: () => console.log('Dashboard') },
{ key: 'Projects', iconName: 'lucide:folder', action: () => console.log('Projects') },
{ key: 'Analytics', iconName: 'lucide:lineChart', action: () => console.log('Analytics') },
{ key: 'Settings', iconName: 'lucide:settings', action: () => console.log('Settings') },
]}
></dees-appui-mainmenu>
`;
@ -35,7 +35,7 @@ export class DeesAppuiMainmenu extends DeesElement {
// INSTANCE
@property({ type: Array })
public tabs: interfaces.ITab[] = [
{ key: '⚠️ Please set tabs', iconName: 'alertTriangle', action: () => console.warn('No tabs configured for mainmenu') },
{ key: '⚠️ Please set tabs', iconName: 'lucide:alertTriangle', action: () => console.warn('No tabs configured for mainmenu') },
];
@property()
@ -112,7 +112,7 @@ export class DeesAppuiMainmenu extends DeesElement {
this.updateTab(tabArg);
}}"
>
<dees-icon .icon="${tabArg.iconName ? `lucide:${tabArg.iconName}` : ''}"></dees-icon>
<dees-icon .icon="${tabArg.iconName || ''}"></dees-icon>
</div>
`;
})}

View File

@ -14,15 +14,94 @@ import * as domtools from '@design.estate/dees-domtools';
@customElement('dees-appui-tabs')
export class DeesAppuiTabs extends DeesElement {
public static demo = () => html`
<dees-appui-tabs
.tabs=${[
{ key: 'Tab 1', action: () => console.log('Tab 1 clicked') },
{ key: 'Tab 2', action: () => console.log('Tab 2 clicked') },
{ key: 'Tab 3', action: () => console.log('Tab 3 clicked') },
]}
></dees-appui-tabs>
`;
public static demo = () => {
const horizontalTabs: interfaces.ITab[] = [
{ key: 'Home', iconName: 'lucide:home', action: () => console.log('Home clicked') },
{ key: 'Analytics Dashboard', iconName: 'lucide:lineChart', action: () => console.log('Analytics clicked') },
{ key: 'Reports', iconName: 'lucide:fileText', action: () => console.log('Reports clicked') },
{ key: 'User Settings', iconName: 'lucide:settings', action: () => console.log('Settings clicked') },
{ key: 'Help', iconName: 'lucide:helpCircle', action: () => console.log('Help clicked') },
];
const verticalTabs: interfaces.ITab[] = [
{ key: 'Profile', iconName: 'lucide:user', action: () => console.log('Profile clicked') },
{ key: 'Security', iconName: 'lucide:shield', action: () => console.log('Security clicked') },
{ key: 'Notifications', iconName: 'lucide:bell', action: () => console.log('Notifications clicked') },
{ key: 'Integrations', iconName: 'lucide:link', action: () => console.log('Integrations clicked') },
{ key: 'Advanced', iconName: 'lucide:code', action: () => console.log('Advanced clicked') },
];
const noIndicatorTabs: interfaces.ITab[] = [
{ key: 'All', action: () => console.log('All clicked') },
{ key: 'Active', action: () => console.log('Active clicked') },
{ key: 'Completed', action: () => console.log('Completed clicked') },
{ key: 'Archived', action: () => console.log('Archived clicked') },
];
const demoContent = (text: string) => html`
<div style="padding: 24px; color: ${cssManager.bdTheme('#71717a', '#a1a1aa')};">
${text}
</div>
`;
return html`
<style>
.demo-container {
display: flex;
flex-direction: column;
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;
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')};
}
.two-column {
display: grid;
grid-template-columns: 200px 1fr;
gap: 24px;
align-items: start;
}
</style>
<div class="demo-container">
<div class="section">
<div class="section-title">Horizontal Tabs with Animated Indicator</div>
<dees-appui-tabs .tabs=${horizontalTabs}>
${demoContent('Select a tab to see the smooth sliding animation of the indicator. The indicator automatically adjusts its width to match the tab content with minimal padding.')}
</dees-appui-tabs>
</div>
<div class="section">
<div class="section-title">Vertical Tabs Layout</div>
<div class="two-column">
<dees-appui-tabs .tabStyle=${'vertical'} .tabs=${verticalTabs}></dees-appui-tabs>
${demoContent('Vertical tabs work great for settings pages and navigation menus. The animated indicator smoothly transitions between selections.')}
</div>
</div>
<div class="section">
<div class="section-title">Without Indicator</div>
<dees-appui-tabs .showTabIndicator=${false} .tabs=${noIndicatorTabs}>
${demoContent('Tabs can also be used without the animated indicator by setting showTabIndicator to false.')}
</dees-appui-tabs>
</div>
</div>
`;
};
// INSTANCE
@property({
@ -50,148 +129,217 @@ export class DeesAppuiTabs extends DeesElement {
.tabs-wrapper {
position: relative;
background: ${cssManager.bdTheme('#f5f5f5', '#000000')};
height: 52px;
}
.tabs-wrapper.horizontal-wrapper {
border-bottom: 1px solid ${cssManager.bdTheme('#e5e7eb', '#27272a')};
}
.tabsContainer {
position: relative;
z-index: 1;
user-select: none;
}
.tabsContainer.horizontal {
display: grid;
padding-top: 20px;
padding-bottom: 0px;
margin-left: 24px;
display: flex;
align-items: center;
font-size: 14px;
overflow-x: auto;
scrollbar-width: none;
height: 48px;
padding: 0 16px;
gap: 4px;
}
.tabsContainer.horizontal::-webkit-scrollbar {
display: none;
}
.tabsContainer.vertical {
display: flex;
flex-direction: column;
padding: 20px;
padding: 8px;
font-size: 14px;
gap: 2px;
position: relative;
background: ${cssManager.bdTheme('#f9fafb', '#18181b')};
border-radius: 8px;
}
.tab {
color: ${cssManager.bdTheme('#666', '#a0a0a0')};
color: ${cssManager.bdTheme('#71717a', '#71717a')};
white-space: nowrap;
cursor: default;
transition: color 0.1s;
cursor: pointer;
transition: color 0.15s ease;
font-weight: 500;
position: relative;
z-index: 2;
}
.horizontal .tab {
margin-right: 30px;
padding-top: 4px;
padding-bottom: 12px;
padding: 0 16px;
height: 100%;
display: inline-flex;
align-items: center;
gap: 8px;
position: relative;
border-radius: 6px 6px 0 0;
transition: background-color 0.15s ease;
}
.vertical .tab {
padding: 12px 16px;
margin-bottom: 4px;
border-radius: 4px;
width: 100%;
display: flex;
.horizontal .tab:not(:last-child)::after {
content: '';
position: absolute;
right: -2px;
top: 50%;
transform: translateY(-50%);
height: 20px;
width: 1px;
background: ${cssManager.bdTheme('#e5e7eb', '#27272a')};
opacity: 0.5;
}
.horizontal .tab .tab-content {
display: inline-flex;
align-items: center;
gap: 8px;
}
.vertical .tab {
padding: 10px 16px;
border-radius: 6px;
width: 100%;
display: flex;
align-items: center;
gap: 8px;
transition: all 0.15s ease;
}
.tab:hover {
color: ${cssManager.bdTheme('#000', '#ffffff')};
color: ${cssManager.bdTheme('#09090b', '#fafafa')};
}
.horizontal .tab:hover {
background: ${cssManager.bdTheme('rgba(0, 0, 0, 0.03)', 'rgba(255, 255, 255, 0.03)')};
}
.horizontal .tab:hover::after,
.horizontal .tab:hover + .tab::after {
opacity: 0;
}
.vertical .tab:hover {
background: ${cssManager.bdTheme('rgba(0, 0, 0, 0.05)', 'rgba(255, 255, 255, 0.05)')};
background: ${cssManager.bdTheme('rgba(244, 244, 245, 0.5)', 'rgba(39, 39, 42, 0.5)')};
}
.tab.selectedTab {
color: ${cssManager.bdTheme('#333', '#e0e0e0')};
.horizontal .tab.selectedTab {
color: ${cssManager.bdTheme('#09090b', '#fafafa')};
}
.horizontal .tab.selectedTab::after,
.horizontal .tab.selectedTab + .tab::after {
opacity: 0;
}
.vertical .tab.selectedTab {
background: ${cssManager.bdTheme('rgba(0, 0, 0, 0.1)', 'rgba(255, 255, 255, 0.1)')};
color: ${cssManager.bdTheme('#000', '#ffffff')};
color: ${cssManager.bdTheme('#09090b', '#fafafa')};
}
.tab dees-icon {
font-size: 16px;
}
.tabs-wrapper .tabIndicator {
.tabIndicator {
position: absolute;
z-index: 0;
left: 40px;
bottom: 0px;
height: 40px;
width: 40px;
background: ${cssManager.bdTheme('#ffffff', '#161616')};
transition: all 0.1s;
border-top-left-radius: 8px;
border-top-right-radius: 8px;
border-top: 1px solid ${cssManager.bdTheme('#e0e0e0', '#444444')};
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
opacity: 0;
}
.tabIndicator.no-transition {
transition: none;
}
.vertical .tabIndicator {
display: none;
.tabs-wrapper .tabIndicator {
height: 3px;
bottom: 0;
background: ${cssManager.bdTheme('#3b82f6', '#3b82f6')};
border-radius: 3px 3px 0 0;
z-index: 3;
}
.vertical-wrapper {
position: relative;
}
.vertical-wrapper .tabIndicator {
left: 8px;
right: 8px;
border-radius: 6px;
background: ${cssManager.bdTheme('#ffffff', '#27272a')};
z-index: 1;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
}
.content {
margin-top: 20px;
padding: 32px 24px;
}
`,
];
public render(): TemplateResult {
return html`
${this.tabStyle === 'horizontal' ? html`
<style>
.tabsContainer.horizontal {
grid-template-columns: repeat(${this.tabs.length}, min-content);
}
</style>
<div class="tabs-wrapper">
<div class="tabsContainer horizontal">
${this.tabs.map((tabArg) => {
return html`
<div
class="tab ${tabArg === this.selectedTab ? 'selectedTab' : ''}"
@click="${() => this.selectTab(tabArg)}"
>
${tabArg.key}
</div>
`;
})}
</div>
${this.showTabIndicator ? html`
<div class="tabIndicator"></div>
` : ''}
</div>
` : html`
<div class="tabsContainer vertical">
${this.tabs.map((tabArg) => {
return html`
<div
class="tab ${tabArg === this.selectedTab ? 'selectedTab' : ''}"
@click="${() => this.selectTab(tabArg)}"
>
${tabArg.iconName ? html`<dees-icon .iconName=${tabArg.iconName}></dees-icon>` : ''}
${tabArg.key}
</div>
`;
})}
</div>
`}
${this.renderTabsWrapper()}
<div class="content">
<slot></slot>
</div>
`;
}
private renderTabsWrapper(): TemplateResult {
const isHorizontal = this.tabStyle === 'horizontal';
const wrapperClass = isHorizontal ? 'tabs-wrapper horizontal-wrapper' : 'vertical-wrapper';
const containerClass = `tabsContainer ${this.tabStyle}`;
return html`
<div class="${wrapperClass}">
<div class="${containerClass}">
${this.tabs.map(tab => this.renderTab(tab, isHorizontal))}
</div>
${this.showTabIndicator ? html`<div class="tabIndicator"></div>` : ''}
</div>
`;
}
private renderTab(tab: interfaces.ITab, isHorizontal: boolean): TemplateResult {
const isSelected = tab === this.selectedTab;
const classes = `tab ${isSelected ? 'selectedTab' : ''}`;
const content = isHorizontal ? html`
<span class="tab-content">
${this.renderTabIcon(tab)}
${tab.key}
</span>
` : html`
${this.renderTabIcon(tab)}
${tab.key}
`;
return html`
<div
class="${classes}"
@click="${() => this.selectTab(tab)}"
>
${content}
</div>
`;
}
private renderTabIcon(tab: interfaces.ITab): TemplateResult | '' {
return tab.iconName ? html`<dees-icon .icon=${tab.iconName}></dees-icon>` : '';
}
private selectTab(tabArg: interfaces.ITab) {
this.selectedTab = tabArg;
this.updateTabIndicator();
tabArg.action();
// Emit tab-select event
@ -202,31 +350,6 @@ export class DeesAppuiTabs extends DeesElement {
}));
}
/**
* updates the indicator position
*/
private updateTabIndicator() {
if (!this.showTabIndicator || this.tabStyle !== 'horizontal' || !this.selectedTab) {
return;
}
const tabIndex = this.tabs.indexOf(this.selectedTab);
const selectedTabElement: HTMLElement = this.shadowRoot.querySelector(
`.tabs-wrapper .tabsContainer .tab:nth-child(${tabIndex + 1})`
);
if (!selectedTabElement) return;
const tabsContainer: HTMLElement = this.shadowRoot.querySelector('.tabs-wrapper .tabsContainer');
const marginLeft = parseInt(window.getComputedStyle(tabsContainer).getPropertyValue("margin-left"));
const tabIndicator: HTMLElement = this.shadowRoot.querySelector('.tabs-wrapper .tabIndicator');
if (tabIndicator) {
tabIndicator.style.width = selectedTabElement.clientWidth + 24 + 'px';
tabIndicator.style.left = selectedTabElement.offsetLeft + marginLeft - 12 + 'px';
}
}
firstUpdated() {
if (this.tabs && this.tabs.length > 0) {
this.selectTab(this.tabs[0]);
@ -241,7 +364,88 @@ export class DeesAppuiTabs extends DeesElement {
}
if (changedProperties.has('selectedTab') || changedProperties.has('tabs')) {
this.updateTabIndicator();
await this.updateComplete;
// Wait for fonts to load on first update
if (!this.indicatorInitialized && document.fonts) {
await document.fonts.ready;
}
requestAnimationFrame(() => {
this.updateTabIndicator();
});
}
}
private indicatorInitialized = false;
private updateTabIndicator() {
if (!this.shouldShowIndicator()) return;
const selectedTabElement = this.getSelectedTabElement();
if (!selectedTabElement) return;
const indicator = this.getIndicatorElement();
if (!indicator) return;
this.handleInitialTransition(indicator);
if (this.tabStyle === 'horizontal') {
this.updateHorizontalIndicator(indicator, selectedTabElement);
} else {
this.updateVerticalIndicator(indicator, selectedTabElement);
}
indicator.style.opacity = '1';
}
private shouldShowIndicator(): boolean {
return this.selectedTab && this.showTabIndicator && this.tabs.includes(this.selectedTab);
}
private getSelectedTabElement(): HTMLElement | null {
const selectedIndex = this.tabs.indexOf(this.selectedTab);
const isHorizontal = this.tabStyle === 'horizontal';
const selector = isHorizontal
? `.tabs-wrapper .tabsContainer .tab:nth-child(${selectedIndex + 1})`
: `.vertical-wrapper .tabsContainer .tab:nth-child(${selectedIndex + 1})`;
return this.shadowRoot.querySelector(selector);
}
private getIndicatorElement(): HTMLElement | null {
return this.shadowRoot.querySelector('.tabIndicator');
}
private handleInitialTransition(indicator: HTMLElement): void {
if (!this.indicatorInitialized) {
indicator.classList.add('no-transition');
this.indicatorInitialized = true;
setTimeout(() => {
indicator.classList.remove('no-transition');
}, 50);
}
}
private updateHorizontalIndicator(indicator: HTMLElement, tabElement: HTMLElement): void {
const tabContent = tabElement.querySelector('.tab-content') as HTMLElement;
if (!tabContent) return;
const wrapperRect = indicator.parentElement.getBoundingClientRect();
const contentRect = tabContent.getBoundingClientRect();
const contentLeft = contentRect.left - wrapperRect.left;
const indicatorWidth = contentRect.width + 8;
const indicatorLeft = contentLeft - 4;
indicator.style.width = `${indicatorWidth}px`;
indicator.style.left = `${indicatorLeft}px`;
}
private updateVerticalIndicator(indicator: HTMLElement, tabElement: HTMLElement): void {
const tabsContainer = this.shadowRoot.querySelector('.vertical-wrapper .tabsContainer') as HTMLElement;
if (!tabsContainer) return;
indicator.style.top = `${tabElement.offsetTop + tabsContainer.offsetTop}px`;
indicator.style.height = `${tabElement.clientHeight}px`;
}
}

View File

@ -35,17 +35,17 @@ export class DeesAppuiView extends DeesElement {
id: 'demo-view',
name: 'Demo View',
description: 'A demonstration view',
iconName: 'home',
iconName: 'lucide:home',
tabs: [
{
key: 'overview',
iconName: 'chart-line',
iconName: 'lucide:lineChart',
action: () => console.log('Overview tab'),
content: html`<div style="padding: 20px;">Overview Content</div>`
},
{
key: 'details',
iconName: 'file-alt',
iconName: 'lucide:fileText',
action: () => console.log('Details tab'),
content: html`<div style="padding: 20px;">Details Content</div>`
}

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`
<style>
.demoContainer {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
background: #222;
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;
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>
<div class="demoContainer">
<dees-chips
selectionMode="none"
.selectableChips=${[
{ key: 'account1', value: 'Payment Account 1' },
{ key: 'account2', value: 'PaymentAccount2' },
{ key: 'account3', value: 'Payment Account 3' },
]}
></dees-chips>
<dees-chips
selectionMode="single"
chipsAreRemovable
.selectableChips=${[
{ key: 'account1', value: 'Payment Account 1' },
{ key: 'account2', value: 'PaymentAccount2' },
{ key: 'account3', value: 'Payment Account 3' },
]}
></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>
<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
selectionMode="none"
.selectableChips=${[
{ key: 'status', value: 'Active' },
{ key: 'tier', value: 'Premium' },
{ key: 'region', value: 'EU-West' },
{ key: 'type', value: 'Enterprise' },
]}
></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
selectionMode="single"
chipsAreRemovable
.selectableChips=${[
{ key: 'env', value: 'Production' },
{ key: 'version', value: '2.4.1' },
{ key: 'branch', value: 'main' },
{ key: 'author', value: 'John Doe' },
]}
></dees-chips>
</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 {
user-select: none;
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.chip {
border-top: ${cssManager.bdTheme('1px solid #CCC', '1px solid #444')};
background: #333333;
background: ${cssManager.bdTheme('#f4f4f5', '#27272a')};
border: 1px solid ${cssManager.bdTheme('#e5e7eb', '#3f3f46')};
display: inline-flex;
height: 20px;
line-height: 20px;
padding: 0px 8px;
font-size: 12px;
color: #fff;
border-radius: 40px;
margin-right: 4px;
margin-bottom: 4px;
align-items: center;
height: 32px;
padding: 0px 12px;
font-size: 14px;
font-weight: 500;
color: ${cssManager.bdTheme('#09090b', '#fafafa')};
border-radius: 6px;
position: relative;
overflow: hidden;
box-shadow: 0px 1px 2px rgba(0, 0, 0, 0.3);
cursor: pointer;
transition: all 0.15s ease;
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
}
.chip:hover {
background: #666666;
background: ${cssManager.bdTheme('#e5e7eb', '#3f3f46')};
border-color: ${cssManager.bdTheme('#d1d5db', '#52525b')};
}
.chip:active {
transform: scale(0.98);
}
.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 {
background: rgba(0, 0, 0, 0.3);
height: 100%;
display: inline-block;
background: ${cssManager.bdTheme('rgba(0, 0, 0, 0.06)', 'rgba(255, 255, 255, 0.1)')};
height: 20px;
line-height: 20px;
display: inline-flex;
align-items: center;
margin-left: -8px;
padding-left: 8px;
padding-right: 8px;
padding: 0px 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 {
padding: 0px 6px 0px 4px;
margin-left: 4px;
margin-right: -8px;
background: rgba(0, 0, 0, 0.3);
display: flex;
align-items: center;
justify-content: center;
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 {
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,19 +180,25 @@ export class DeesChips extends DeesElement {
}
public async firstUpdated() {
if (!this.textContent) {
this.textContent = 'Button';
this.performUpdate();
}
// Component initialized
}
private isSelected(chip: Tag): boolean {
if (this.selectionMode === 'single') {
return this.selectedChip?.key === chip.key;
return this.selectedChip ? this.isSameChip(this.selectedChip, chip) : false;
} 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) {
if (this.selectionMode === 'none') {
@ -168,7 +215,7 @@ export class DeesChips extends DeesElement {
}
} else if (this.selectionMode === 'multiple') {
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 {
this.selectedChips = [...this.selectedChips, chip];
}
@ -179,13 +226,13 @@ export class DeesChips extends DeesElement {
public removeChip(chipToRemove: Tag): void {
// 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
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 (this.selectedChip && this.selectedChip.key === chipToRemove.key) {
if (this.selectedChip && this.isSameChip(this.selectedChip, chipToRemove)) {
this.selectedChip = null;
}

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>
.demoWrapper {
box-sizing: border-box;
position: absolute;
width: 100%;
height: 100%;
padding: 20px;
background: none;
export const demoFunc = () => html`
<style>
.demoWrapper {
box-sizing: border-box;
position: relative;
width: 100%;
min-height: 100vh;
padding: 48px;
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">
<dees-dataview-codebox proglang="typescript">
import * as text from './hello'; const hiThere = 'nice'; const myFunction = async () => {
console.log('nice one'); }
</dees-dataview-codebox>
</div>`
class UserService {
private users: User[] = [];
constructor(private apiUrl: string) {
console.log('UserService initialized');
}
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

@ -53,23 +53,23 @@ export class DeesDataviewCodebox extends DeesElement {
}
.mainbox {
position: relative;
color: ${this.goBright ? '#333333' : '#ffffff'};
border-top: 1px solid ${this.goBright ? '#ffffff' : '#333333'};
box-shadow: 0px 0px 5px ${this.goBright ? 'rgba(0,0,0,0.1)' : 'rgba(0,0,0,0.5)'};
background: ${this.goBright ? '#ffffff' : '#191919'};
border-radius: 16px;
color: ${cssManager.bdTheme('#09090b', '#fafafa')};
border: 1px solid ${cssManager.bdTheme('#e5e7eb', '#27272a')};
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1), 0 1px 2px rgba(0, 0, 0, 0.06);
background: ${cssManager.bdTheme('#ffffff', '#09090b')};
border-radius: 6px;
overflow: hidden;
}
.appbar {
position: relative;
color: ${cssManager.bdTheme('#333', '#ccc')};
background: ${cssManager.bdTheme('#ffffff', '#161616')};
border-bottom: 1px solid ${cssManager.bdTheme('#eeeeeb', '#222222')};
height: 24px;
color: ${cssManager.bdTheme('#71717a', '#a1a1aa')};
background: ${cssManager.bdTheme('#f9fafb', '#18181b')};
border-bottom: 1px solid ${cssManager.bdTheme('#e5e7eb', '#27272a')};
height: 32px;
display: flex;
font-size: 12px;
line-height: 24px;
font-size: 13px;
line-height: 32px;
justify-content: center;
align-items: center;
}
@ -82,31 +82,38 @@ export class DeesDataviewCodebox extends DeesElement {
}
.bottomBar {
color: ${cssManager.bdTheme('#333', '#ccc')};
background: ${cssManager.bdTheme('#ffffff', '#161616')};
border-top: 1px solid ${cssManager.bdTheme('#eeeeeb', '#222222')};
height: 24px;
position: relative;
color: ${cssManager.bdTheme('#71717a', '#a1a1aa')};
background: ${cssManager.bdTheme('#f9fafb', '#18181b')};
border-top: 1px solid ${cssManager.bdTheme('#e5e7eb', '#27272a')};
height: 28px;
font-size: 12px;
line-height: 24px;
text-align: right;
padding-right: 100px;
line-height: 28px;
display: flex;
justify-content: flex-end;
align-items: stretch;
overflow: hidden;
}
.spacesLabel {
padding: 0 16px;
display: flex;
align-items: center;
}
.languageLabel {
color: ${cssManager.bdTheme('#333', '#ccc')};
color: ${cssManager.bdTheme('#3b82f6', '#3b82f6')};
font-size: 12px;
line-height: 24px;
z-index: 10;
background: #6596ff20;
display: inline-block;
position: absolute;
bottom: 0px;
right: 0px;
padding: 0px 16px 0px 8px;
line-height: 28px;
background: ${cssManager.bdTheme('rgba(59, 130, 246, 0.1)', 'rgba(59, 130, 246, 0.1)')};
padding: 0px 16px;
font-weight: 500;
display: flex;
align-items: center;
}
.hljs-keyword {
color: #ff65ec;
color: ${cssManager.bdTheme('#dc2626', '#f87171')};
}
.codegrid {
@ -116,10 +123,10 @@ export class DeesDataviewCodebox extends DeesElement {
}
.lineNumbers {
color: ${this.goBright ? '#acacac' : '#666666'};
padding: 30px 16px 0px 0px;
color: ${cssManager.bdTheme('#71717a', '#52525b')};
padding: 24px 16px 0px 0px;
text-align: right;
border-right: 1px solid ${this.goBright ? '#eaeaea' : '#222222'};
border-right: 1px solid ${cssManager.bdTheme('#e5e7eb', '#27272a')};
}
.lineCounter:last-child {
@ -129,11 +136,11 @@ export class DeesDataviewCodebox extends DeesElement {
pre {
overflow-x: auto;
margin: 0px;
padding: 30px 40px;
padding: 24px 24px;
}
code {
font-weight: ${this.goBright ? '400' : '300'};
font-weight: 400;
padding: 0px;
margin: 0px;
}
@ -147,23 +154,39 @@ export class DeesDataviewCodebox extends DeesElement {
}
.hljs-string {
color: #ffa465;
color: ${cssManager.bdTheme('#059669', '#10b981')};
}
.hljs-built_in {
color: #65ff6a;
color: ${cssManager.bdTheme('#8b5cf6', '#a78bfa')};
}
.hljs-function {
color: ${this.goBright ? '#2765DF' : '#6596ff'};
color: ${cssManager.bdTheme('#3b82f6', '#60a5fa')};
}
.hljs-params {
color: ${this.goBright ? '#3DB420' : '#65d5ff'};
color: ${cssManager.bdTheme('#0891b2', '#06b6d4')};
}
.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>
<div
@ -198,7 +221,7 @@ export class DeesDataviewCodebox extends DeesElement {
<pre><code></code></pre>
</div>
<div class="bottomBar">
Spaces: 2
<div class="spacesLabel">Spaces: 2</div>
<div class="languageLabel">${this.progLang}</div>
</div>
</div>

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`
<dees-demowrapper>
@ -7,10 +7,31 @@ export const demoFunc = () => html`
.demo-container {
display: flex;
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;
max-width: 1200px;
margin: 0 auto;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
.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 {
@ -28,7 +49,10 @@ export const demoFunc = () => html`
</style>
<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
.label=${'Display Mode'}
.description=${'Choose how content is displayed'}
@ -36,15 +60,20 @@ export const demoFunc = () => html`
.selectedOption=${'Grid View'}
></dees-input-multitoggle>
<br><br>
<dees-input-multitoggle
.label=${'T-Shirt Size'}
.description=${'Select your preferred size'}
.options=${['XS', 'S', 'M', 'L', 'XL', 'XXL']}
.selectedOption=${'M'}
></dees-input-multitoggle>
</dees-panel>
</div>
<dees-panel .title=${'Boolean Toggle'} .subtitle=${'Simple on/off switches with custom labels'}>
<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-input-multitoggle
.label=${'Notifications'}
.description=${'Enable or disable push notifications'}
@ -52,6 +81,8 @@ export const demoFunc = () => html`
.selectedOption=${'true'}
></dees-input-multitoggle>
<br><br>
<dees-input-multitoggle
.label=${'Theme Mode'}
.description=${'Switch between light and dark theme'}
@ -60,13 +91,15 @@ export const demoFunc = () => html`
.booleanFalseName=${'Light'}
.selectedOption=${'Dark'}
></dees-input-multitoggle>
</dees-panel>
</div>
<dees-panel .title=${'Settings Panel'} .subtitle=${'Configuration options in a horizontal layout'}>
<div class="section">
<div class="section-title">Settings Grid</div>
<div class="section-description">Configuration options arranged in a responsive grid layout.</div>
<div class="settings-grid">
<dees-input-multitoggle
.label=${'Auto-Save'}
.layoutMode=${'horizontal'}
.type=${'boolean'}
.booleanTrueName=${'Enabled'}
.booleanFalseName=${'Disabled'}
@ -75,30 +108,30 @@ export const demoFunc = () => html`
<dees-input-multitoggle
.label=${'Language'}
.layoutMode=${'horizontal'}
.options=${['English', 'German', 'French', 'Spanish']}
.selectedOption=${'English'}
></dees-input-multitoggle>
<dees-input-multitoggle
.label=${'Quality'}
.layoutMode=${'horizontal'}
.options=${['Low', 'Medium', 'High', 'Ultra']}
.selectedOption=${'High'}
></dees-input-multitoggle>
<dees-input-multitoggle
.label=${'Privacy'}
.layoutMode=${'horizontal'}
.type=${'boolean'}
.booleanTrueName=${'Private'}
.booleanFalseName=${'Public'}
.selectedOption=${'Private'}
></dees-input-multitoggle>
</div>
</dees-panel>
</div>
<dees-panel .title=${'States & Form Integration'} .subtitle=${'Disabled states and form usage'}>
<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-input-multitoggle
.label=${'Account Type'}
.description=${'This setting is locked'}
@ -107,6 +140,8 @@ export const demoFunc = () => html`
.disabled=${true}
></dees-input-multitoggle>
<br><br>
<dees-form>
<dees-input-text .label=${'Project Name'} .required=${true}></dees-input-text>
<dees-input-multitoggle
@ -122,7 +157,7 @@ export const demoFunc = () => html`
.selectedOption=${'MIT'}
></dees-input-multitoggle>
</dees-form>
</dees-panel>
</div>
</div>
</dees-demowrapper>
`;

View File

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

View File

@ -139,12 +139,12 @@ export class DeesModal extends DeesElement {
opacity: 0;
min-height: 120px;
max-height: calc(100vh - 40px);
background: ${cssManager.bdTheme('#ffffff', '#111')};
border-radius: 8px;
border: 1px solid ${cssManager.bdTheme('#e0e0e0', '#333')};
transition: all 0.2s;
background: ${cssManager.bdTheme('#ffffff', '#09090b')};
border-radius: 6px;
border: 1px solid ${cssManager.bdTheme('#e5e7eb', '#27272a')};
transition: all 0.2s ease;
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;
display: flex;
flex-direction: column;
@ -193,6 +193,7 @@ export class DeesModal extends DeesElement {
max-height: 100vh !important;
margin: 0;
border-radius: 0;
border: none;
}
}
@ -214,7 +215,7 @@ export class DeesModal extends DeesElement {
align-items: center;
justify-content: space-between;
padding: 0 12px;
border-bottom: 1px solid ${cssManager.bdTheme('#e0e0e0', '#333')};
border-bottom: 1px solid ${cssManager.bdTheme('#e5e7eb', '#27272a')};
position: relative;
flex-shrink: 0;
}
@ -232,23 +233,23 @@ export class DeesModal extends DeesElement {
.modal .heading .header-button {
width: 28px;
height: 28px;
border-radius: 6px;
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.2s;
transition: all 0.15s ease;
background: transparent;
color: ${cssManager.bdTheme('#666', '#999')};
color: ${cssManager.bdTheme('#71717a', '#a1a1aa')};
}
.modal .heading .header-button:hover {
background: ${cssManager.bdTheme('rgba(0, 0, 0, 0.08)', 'rgba(255, 255, 255, 0.08)')};
color: ${cssManager.bdTheme('#333', '#fff')};
background: ${cssManager.bdTheme('#f4f4f5', '#27272a')};
color: ${cssManager.bdTheme('#09090b', '#fafafa')};
}
.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 {
@ -264,6 +265,7 @@ export class DeesModal extends DeesElement {
font-size: 14px;
line-height: 40px;
padding: 0 40px;
color: ${cssManager.bdTheme('#09090b', '#fafafa')};
}
.modal .content {
@ -276,7 +278,7 @@ export class DeesModal extends DeesElement {
.modal .bottomButtons {
display: flex;
flex-direction: row;
border-top: 1px solid ${cssManager.bdTheme('#e0e0e0', '#333')};
border-top: 1px solid ${cssManager.bdTheme('#e5e7eb', '#27272a')};
justify-content: flex-end;
gap: 8px;
padding: 8px;
@ -285,39 +287,43 @@ export class DeesModal extends DeesElement {
.modal .bottomButtons .bottomButton {
padding: 8px 16px;
border-radius: 6px;
border-radius: 4px;
line-height: 16px;
text-align: center;
font-size: 14px;
font-weight: 500;
cursor: pointer;
user-select: none;
transition: all 0.2s;
background: ${cssManager.bdTheme('rgba(0, 0, 0, 0.05)', 'rgba(255, 255, 255, 0.05)')};
transition: all 0.15s ease;
background: ${cssManager.bdTheme('#ffffff', '#27272a')};
border: 1px solid ${cssManager.bdTheme('#e5e7eb', '#3f3f46')};
color: ${cssManager.bdTheme('#09090b', '#fafafa')};
white-space: nowrap;
}
.modal .bottomButtons .bottomButton:hover {
background: ${cssManager.bdTheme(colors.bright.blue, colors.dark.blue)};
color: #ffffff;
background: ${cssManager.bdTheme('#f4f4f5', '#3f3f46')};
border-color: ${cssManager.bdTheme('#d1d5db', '#52525b')};
}
.modal .bottomButtons .bottomButton:active {
background: ${cssManager.bdTheme(colors.bright.blueActive, colors.dark.blueActive)};
color: #ffffff;
background: ${cssManager.bdTheme('#e5e7eb', '#52525b')};
}
.modal .bottomButtons .bottomButton:last-child {
border-right: none;
}
.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;
}
.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 {
background: ${cssManager.bdTheme(colors.bright.blueMuted, colors.dark.blueMuted)};
background: ${cssManager.bdTheme('#1d4ed8', '#1d4ed8')};
border-color: ${cssManager.bdTheme('#1d4ed8', '#1d4ed8')};
}
`,
];