diff --git a/dev/tabs.html b/dev/tabs.html index 829e3ef3fb..d960a874ec 100644 --- a/dev/tabs.html +++ b/dev/tabs.html @@ -9,33 +9,117 @@ + + - - - Analytics - - - - Customers - - - - Dashboards - - - - Documents - - - - Orders - - - + + + + + + + + + +
+ + Analytics + Customers + Dashboards + Documents + Orders + +
diff --git a/packages/tabs/src/styles/vaadin-tab-base-styles.d.ts b/packages/tabs/src/styles/vaadin-tab-base-styles.d.ts new file mode 100644 index 0000000000..6f6b7dcd1a --- /dev/null +++ b/packages/tabs/src/styles/vaadin-tab-base-styles.d.ts @@ -0,0 +1,8 @@ +/** + * @license + * Copyright (c) 2025 - 2025 Vaadin Ltd. + * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/ + */ +import type { CSSResult } from 'lit'; + +export const tabStyles: CSSResult; diff --git a/packages/tabs/src/styles/vaadin-tab-base-styles.js b/packages/tabs/src/styles/vaadin-tab-base-styles.js new file mode 100644 index 0000000000..172f8d7296 --- /dev/null +++ b/packages/tabs/src/styles/vaadin-tab-base-styles.js @@ -0,0 +1,93 @@ +/** + * @license + * Copyright (c) 2025 - 2025 Vaadin Ltd. + * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/ + */ +import '@vaadin/component-base/src/style-props.js'; +import { css } from 'lit'; + +export const tabStyles = css` + @layer base { + :host { + flex-shrink: 0; + display: flex; + align-items: center; + justify-content: center; + gap: var(--vaadin-tab-gap, var(--_vaadin-gap-container-inline)); + padding: var(--vaadin-tab-padding, var(--_vaadin-padding-container)); + cursor: var(--vaadin-clickable-cursor); + font-size: var(--vaadin-tab-font-size, 1em); + font-weight: var(--vaadin-tab-font-weight, 500); + line-height: var(--vaadin-tab-line-height, inherit); + color: var(--vaadin-tab-color, inherit); + background: var(--vaadin-tab-background, transparent); + border-radius: var(--vaadin-tab-border-radius, var(--_vaadin-radius-m)); + -webkit-tap-highlight-color: transparent; + -webkit-user-select: none; + user-select: none; + touch-action: manipulation; + position: relative; + } + + :host([hidden]) { + display: none !important; + } + + :host([orientation='vertical']) { + justify-content: start; + } + + :host([selected]) { + --vaadin-tab-background: var(--_vaadin-background-container); + --vaadin-tab-color: var(--_vaadin-color-strong); + } + + :host([disabled]) { + cursor: var(--vaadin-disabled-cursor); + opacity: 0.5; + } + + :host(:is([focus-ring], :focus-visible)) { + outline: var(--vaadin-focus-ring-width) solid var(--vaadin-focus-ring-color); + outline-offset: calc(var(--vaadin-focus-ring-width) * -1); + } + + slot { + gap: inherit; + align-items: inherit; + justify-content: inherit; + } + + ::slotted(a) { + color: inherit; + cursor: inherit; + text-decoration: inherit; + display: flex; + align-items: inherit; + justify-content: inherit; + gap: inherit; + } + + ::slotted(a)::before { + content: ''; + position: absolute; + inset: 0; + } + + @media (forced-colors: active) { + :host { + border: 1px solid Canvas; + } + + :host([selected]) { + color: Highlight; + border-color: Highlight; + } + + :host([disabled]) { + color: GrayText; + opacity: 1; + } + } + } +`; diff --git a/packages/tabs/src/styles/vaadin-tabs-base-styles.d.ts b/packages/tabs/src/styles/vaadin-tabs-base-styles.d.ts new file mode 100644 index 0000000000..1f46396f0f --- /dev/null +++ b/packages/tabs/src/styles/vaadin-tabs-base-styles.d.ts @@ -0,0 +1,8 @@ +/** + * @license + * Copyright (c) 2025 - 2025 Vaadin Ltd. + * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/ + */ +import type { CSSResult } from 'lit'; + +export const tabsStyles: CSSResult; diff --git a/packages/tabs/src/styles/vaadin-tabs-base-styles.js b/packages/tabs/src/styles/vaadin-tabs-base-styles.js new file mode 100644 index 0000000000..d4b60c6bc0 --- /dev/null +++ b/packages/tabs/src/styles/vaadin-tabs-base-styles.js @@ -0,0 +1,108 @@ +/** + * @license + * Copyright (c) 2025 - 2025 Vaadin Ltd. + * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/ + */ +import '@vaadin/component-base/src/style-props.js'; +import { css } from 'lit'; + +export const tabsStyles = css` + @layer base { + :host { + display: flex; + max-width: 100%; + max-height: 100%; + position: relative; + } + + :host([hidden]) { + display: none !important; + } + + :host([orientation='vertical']) { + flex-direction: column; + } + + [part='tabs'] { + flex: 1; + overflow: auto; + overscroll-behavior: contain; + display: flex; + flex-direction: column; + gap: var(--vaadin-tabs-gap, var(--_vaadin-gap-container-inline)); + } + + :host([orientation='horizontal']) [part='tabs'] { + flex-direction: row; + scrollbar-width: none; + } + + /* scrollbar-width is supported in Safari 18.2, use the following for earlier */ + :host([orientation='horizontal']) [part='tabs']::-webkit-scrollbar { + display: none; + } + + [part='back-button'], + [part='forward-button'] { + position: absolute; + z-index: 1; + pointer-events: none; + opacity: 0; + cursor: var(--vaadin-clickable-cursor); + box-sizing: border-box; + height: 100%; + padding: var(--vaadin-tab-padding, var(--_vaadin-padding-container)); + background: var(--_vaadin-background); + display: flex; + align-items: center; + justify-content: center; + -webkit-tap-highlight-color: transparent; + touch-action: manipulation; + } + + [part='forward-button'] { + inset-inline-end: 0; + } + + :host([overflow~='start']) [part='back-button'], + :host([overflow~='end']) [part='forward-button'] { + pointer-events: auto; + opacity: 1; + } + + :is([part='back-button'], [part='forward-button'])::before { + content: ''; + display: block; + width: var(--vaadin-icon-size, 1lh); + height: var(--vaadin-icon-size, 1lh); + background: currentColor; + mask-image: var(--_vaadin-icon-chevron-down); + rotate: 90deg; + } + + [part='forward-button']::before { + rotate: -90deg; + } + + :host(:is([orientation='vertical'], [theme~='hide-scroll-buttons'])) + :is([part='back-button'], [part='forward-button']) { + display: none; + } + + @media (pointer: coarse) { + :host(:not([theme~='show-scroll-buttons'])) :is([part='back-button'], [part='forward-button']) { + display: none; + } + } + + :host([dir='rtl']) :is([part='back-button'], [part='forward-button'])::before { + scale: 1 -1; + } + + @media (forced-colors: active) { + :is([part='back-button'], [part='forward-button'])::before { + background: CanvasText; + } + } + } +`; diff --git a/packages/tabs/test/visual/base/screenshots/tabs/baseline/anchors-horizontal.png b/packages/tabs/test/visual/base/screenshots/tabs/baseline/anchors-horizontal.png new file mode 100644 index 0000000000..9e6791367f Binary files /dev/null and b/packages/tabs/test/visual/base/screenshots/tabs/baseline/anchors-horizontal.png differ diff --git a/packages/tabs/test/visual/base/screenshots/tabs/baseline/anchors-vertical.png b/packages/tabs/test/visual/base/screenshots/tabs/baseline/anchors-vertical.png new file mode 100644 index 0000000000..78d8e88a1a Binary files /dev/null and b/packages/tabs/test/visual/base/screenshots/tabs/baseline/anchors-vertical.png differ diff --git a/packages/tabs/test/visual/base/screenshots/tabs/baseline/ltr-horizontal-end.png b/packages/tabs/test/visual/base/screenshots/tabs/baseline/ltr-horizontal-end.png new file mode 100644 index 0000000000..af6c08702d Binary files /dev/null and b/packages/tabs/test/visual/base/screenshots/tabs/baseline/ltr-horizontal-end.png differ diff --git a/packages/tabs/test/visual/base/screenshots/tabs/baseline/ltr-horizontal-middle.png b/packages/tabs/test/visual/base/screenshots/tabs/baseline/ltr-horizontal-middle.png new file mode 100644 index 0000000000..98ac934ec3 Binary files /dev/null and b/packages/tabs/test/visual/base/screenshots/tabs/baseline/ltr-horizontal-middle.png differ diff --git a/packages/tabs/test/visual/base/screenshots/tabs/baseline/ltr-horizontal-scroll.png b/packages/tabs/test/visual/base/screenshots/tabs/baseline/ltr-horizontal-scroll.png new file mode 100644 index 0000000000..efe6283c88 Binary files /dev/null and b/packages/tabs/test/visual/base/screenshots/tabs/baseline/ltr-horizontal-scroll.png differ diff --git a/packages/tabs/test/visual/base/screenshots/tabs/baseline/ltr-horizontal-start.png b/packages/tabs/test/visual/base/screenshots/tabs/baseline/ltr-horizontal-start.png new file mode 100644 index 0000000000..9e6791367f Binary files /dev/null and b/packages/tabs/test/visual/base/screenshots/tabs/baseline/ltr-horizontal-start.png differ diff --git a/packages/tabs/test/visual/base/screenshots/tabs/baseline/ltr-vertical-end.png b/packages/tabs/test/visual/base/screenshots/tabs/baseline/ltr-vertical-end.png new file mode 100644 index 0000000000..1b057c1444 Binary files /dev/null and b/packages/tabs/test/visual/base/screenshots/tabs/baseline/ltr-vertical-end.png differ diff --git a/packages/tabs/test/visual/base/screenshots/tabs/baseline/ltr-vertical-middle.png b/packages/tabs/test/visual/base/screenshots/tabs/baseline/ltr-vertical-middle.png new file mode 100644 index 0000000000..71338addad Binary files /dev/null and b/packages/tabs/test/visual/base/screenshots/tabs/baseline/ltr-vertical-middle.png differ diff --git a/packages/tabs/test/visual/base/screenshots/tabs/baseline/ltr-vertical-scroll.png b/packages/tabs/test/visual/base/screenshots/tabs/baseline/ltr-vertical-scroll.png new file mode 100644 index 0000000000..13d15feabf Binary files /dev/null and b/packages/tabs/test/visual/base/screenshots/tabs/baseline/ltr-vertical-scroll.png differ diff --git a/packages/tabs/test/visual/base/screenshots/tabs/baseline/ltr-vertical-start.png b/packages/tabs/test/visual/base/screenshots/tabs/baseline/ltr-vertical-start.png new file mode 100644 index 0000000000..6026470c59 Binary files /dev/null and b/packages/tabs/test/visual/base/screenshots/tabs/baseline/ltr-vertical-start.png differ diff --git a/packages/tabs/test/visual/base/screenshots/tabs/baseline/rtl-horizontal-end.png b/packages/tabs/test/visual/base/screenshots/tabs/baseline/rtl-horizontal-end.png new file mode 100644 index 0000000000..6e44fc3989 Binary files /dev/null and b/packages/tabs/test/visual/base/screenshots/tabs/baseline/rtl-horizontal-end.png differ diff --git a/packages/tabs/test/visual/base/screenshots/tabs/baseline/rtl-horizontal-middle.png b/packages/tabs/test/visual/base/screenshots/tabs/baseline/rtl-horizontal-middle.png new file mode 100644 index 0000000000..c8c4f06a96 Binary files /dev/null and b/packages/tabs/test/visual/base/screenshots/tabs/baseline/rtl-horizontal-middle.png differ diff --git a/packages/tabs/test/visual/base/screenshots/tabs/baseline/rtl-horizontal-scroll.png b/packages/tabs/test/visual/base/screenshots/tabs/baseline/rtl-horizontal-scroll.png new file mode 100644 index 0000000000..932a01a7dd Binary files /dev/null and b/packages/tabs/test/visual/base/screenshots/tabs/baseline/rtl-horizontal-scroll.png differ diff --git a/packages/tabs/test/visual/base/screenshots/tabs/baseline/rtl-horizontal-start.png b/packages/tabs/test/visual/base/screenshots/tabs/baseline/rtl-horizontal-start.png new file mode 100644 index 0000000000..bd7bc1f0ba Binary files /dev/null and b/packages/tabs/test/visual/base/screenshots/tabs/baseline/rtl-horizontal-start.png differ diff --git a/packages/tabs/test/visual/base/screenshots/tabs/baseline/rtl-vertical-end.png b/packages/tabs/test/visual/base/screenshots/tabs/baseline/rtl-vertical-end.png new file mode 100644 index 0000000000..46ea8be258 Binary files /dev/null and b/packages/tabs/test/visual/base/screenshots/tabs/baseline/rtl-vertical-end.png differ diff --git a/packages/tabs/test/visual/base/screenshots/tabs/baseline/rtl-vertical-middle.png b/packages/tabs/test/visual/base/screenshots/tabs/baseline/rtl-vertical-middle.png new file mode 100644 index 0000000000..4eb8a929b8 Binary files /dev/null and b/packages/tabs/test/visual/base/screenshots/tabs/baseline/rtl-vertical-middle.png differ diff --git a/packages/tabs/test/visual/base/screenshots/tabs/baseline/rtl-vertical-scroll.png b/packages/tabs/test/visual/base/screenshots/tabs/baseline/rtl-vertical-scroll.png new file mode 100644 index 0000000000..bdddbc990b Binary files /dev/null and b/packages/tabs/test/visual/base/screenshots/tabs/baseline/rtl-vertical-scroll.png differ diff --git a/packages/tabs/test/visual/base/screenshots/tabs/baseline/rtl-vertical-start.png b/packages/tabs/test/visual/base/screenshots/tabs/baseline/rtl-vertical-start.png new file mode 100644 index 0000000000..48bdfd64af Binary files /dev/null and b/packages/tabs/test/visual/base/screenshots/tabs/baseline/rtl-vertical-start.png differ diff --git a/packages/tabs/test/visual/base/tabs.test.js b/packages/tabs/test/visual/base/tabs.test.js new file mode 100644 index 0000000000..80e9844fcc --- /dev/null +++ b/packages/tabs/test/visual/base/tabs.test.js @@ -0,0 +1,166 @@ +import { nextFrame } from '@vaadin/testing-helpers'; +import { fixtureSync } from '@vaadin/testing-helpers/dist/fixture.js'; +import { visualDiff } from '@web/test-runner-visual-regression'; +import '../../../src/vaadin-tabs.js'; + +describe('tabs', () => { + let div, element; + + beforeEach(() => { + div = document.createElement('div'); + div.style.padding = '10px'; + }); + + ['ltr', 'rtl'].forEach((dir) => { + describe(dir, () => { + before(() => { + document.documentElement.setAttribute('dir', dir); + }); + + after(() => { + document.documentElement.removeAttribute('dir'); + }); + + describe(`${dir}-horizontal`, () => { + beforeEach(() => { + element = fixtureSync( + ` + + Foo + Bar + Baz + + `, + div, + ); + }); + + it('start', async () => { + await visualDiff(div, `${dir}-horizontal-start`); + }); + + it('middle', async () => { + element.selected = 1; + await visualDiff(div, `${dir}-horizontal-middle`); + }); + + it('end', async () => { + element.selected = 2; + await visualDiff(div, `${dir}-horizontal-end`); + }); + }); + + describe(`${dir}-vertical`, () => { + beforeEach(() => { + element = fixtureSync( + ` + + Foo + Bar + Baz + + `, + div, + ); + }); + + it('start', async () => { + await visualDiff(div, `${dir}-vertical-start`); + }); + + it('middle', async () => { + element.selected = 1; + await visualDiff(div, `${dir}-vertical-middle`); + }); + + it('end', async () => { + element.selected = 2; + await visualDiff(div, `${dir}-vertical-end`); + }); + }); + }); + }); + + describe('anchors', () => { + beforeEach(() => { + element = fixtureSync( + ` + + Foo + Bar + Baz + + `, + div, + ); + }); + + it('horizontal', async () => { + await visualDiff(div, 'anchors-horizontal'); + }); + + it('vertical', async () => { + element.orientation = 'vertical'; + await visualDiff(div, 'anchors-vertical'); + }); + }); + + describe('scroll', () => { + ['horizontal', 'vertical'].forEach((orientation) => { + describe(orientation, () => { + beforeEach(async () => { + if (orientation === 'vertical') { + div.style.height = '150px'; + div.style.display = 'inline-flex'; + } else { + div.style.width = '400px'; + } + + element = fixtureSync( + ` + + Tab-00 + Tab-01 + Tab-02 + Tab-03 + Tab-04 + Tab-05 + Tab-06 + Tab-07 + Tab-08 + Tab-09 + Tab-10 + Tab-11 + Tab-12 + Tab-13 + Tab-14 + Tab-15 + + `, + div, + ); + + await nextFrame(); + }); + + ['ltr', 'rtl'].forEach((dir) => { + describe(dir, () => { + before(() => { + document.documentElement.setAttribute('dir', dir); + }); + + after(() => { + document.documentElement.removeAttribute('dir'); + }); + + it('selected', async () => { + element.orientation = orientation; + element.selected = 8; + await visualDiff(div, `${dir}-${orientation}-scroll`); + }); + }); + }); + }); + }); + }); +});