diff --git a/changelog.md b/changelog.md
index b5c3dca..647d328 100644
--- a/changelog.md
+++ b/changelog.md
@@ -1,5 +1,18 @@
# 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
+- Adjusted tab spacing to start more to the left with tighter padding
+- 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
diff --git a/test-output.log b/test-output.log
new file mode 100644
index 0000000..25476f4
--- /dev/null
+++ b/test-output.log
@@ -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
+
+[38;5;231m
+π Test Discovery[0m
+[38;5;231m Mode: file[0m
+[38;5;231m Pattern: test/test.tabs-indicator.browser.ts[0m
+[38;5;113m Found: 1 test file(s)[0m
+[38;5;33m
+βΆοΈ test/test.tabs-indicator.browser.ts (1/1)[0m
+[38;5;231m Runtime: chromium[0m
+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
+[38;5;231m [38;5;116mTest starting: tabs indicator positioning debug[0m[0m
+[38;5;231m !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!![0m
+[38;5;231m Using globalThis.tapPromise[0m
+[38;5;231m !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!![0m
+connection ended
+removed connection. 0 sockets remaining.
+[38;5;33m=> [0m Stopped [38;5;215mtest/test.tabs-indicator.browser.ts[0m chromium instance and server.
+[38;5;196m
+β οΈ Error[0m
+[38;5;196m Only 0 out of 1 completed![0m
+[38;5;196m
+β οΈ Error[0m
+[38;5;196m The amount of received tests and expectedTests is unequal! Therefore the testfile failed[0m
+[38;5;196m Summary: -1 passed, 1 failed of 0 tests in 2.7s[0m
+[38;5;231m
+π Test Summary[0m
+[38;5;231mββββββββββββββββββββββββββββββββββ[0m
+[38;5;231mβ Total Files: 1 β[0m
+[38;5;231mβ Total Tests: 0 β[0m
+[38;5;113mβ Passed: 0 β[0m
+[38;5;113mβ Failed: 0 β[0m
+[38;5;231mβ Duration: 4.2s β[0m
+[38;5;231mββββββββββββββββββββββββββββββββββ[0m
+[38;5;116m
+β±οΈ Performance Metrics:[0m
+[38;5;231m Average per test: 0ms[0m
+[38;5;113m
+ALL TESTS PASSED! π[0m
+Exited NOT OK!
+βELIFECYCLEβ Test failed. See above for more details.
diff --git a/test/test.tabs-indicator.browser.ts b/test/test.tabs-indicator.browser.ts
new file mode 100644
index 0000000..f9d1251
--- /dev/null
+++ b/test/test.tabs-indicator.browser.ts
@@ -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();
\ No newline at end of file
diff --git a/ts_web/elements/dees-appui-tabs.ts b/ts_web/elements/dees-appui-tabs.ts
index 4951da5..66a40c6 100644
--- a/ts_web/elements/dees-appui-tabs.ts
+++ b/ts_web/elements/dees-appui-tabs.ts
@@ -15,13 +15,92 @@ import * as domtools from '@design.estate/dees-domtools';
@customElement('dees-appui-tabs')
export class DeesAppuiTabs extends DeesElement {
public static demo = () => html`
-