Skip to content

Commit

Permalink
fix(tabs): bring selected tab into view (#4032)
Browse files Browse the repository at this point in the history
* fix(tabs): adds optional property to bring selected tab into view when first rendered

* fix(tabs): implement review

* test: add unit test

* chore(tabs): update golden image cache

* chore(tabs): adds description and edge case

* chore(tabs): implement code review

* chore(tabs): remove unnecessary attribute from unit tests

* chore(tabs): update golden image

* chore(tabs): always autoscroll

* chore(tabs): golden image cache

* chore(tabs): golden image cache

* chore(tabs): revert config.yml

* chore(tabs): update config.yml

---------

Co-authored-by: rmanole <rmanole@adobe.com>
  • Loading branch information
Rocss and rmanole committed Feb 22, 2024
1 parent 7311822 commit a187057
Show file tree
Hide file tree
Showing 6 changed files with 116 additions and 17 deletions.
2 changes: 1 addition & 1 deletion .circleci/config.yml
Expand Up @@ -10,7 +10,7 @@ executors:
parameters:
current_golden_images_hash:
type: string
default: 058e55ea2c05e5ab919bc7c9f9fc9ea2d5f6f816
default: 429b9158c0fa017239c2a245e2c2fd5f89a7cea7
wireit_cache_name:
type: string
default: wireit
Expand Down
31 changes: 29 additions & 2 deletions packages/tabs/src/Tabs.ts
Expand Up @@ -15,6 +15,7 @@ import {
CSSResult,
CSSResultArray,
html,
PropertyValueMap,
PropertyValues,
SizedMixin,
TemplateResult,
Expand Down Expand Up @@ -229,15 +230,41 @@ export class Tabs extends SizedMixin(Focusable, { noDefaultSize: true }) {
return {};
}

protected override manageAutoFocus(): void {
override async getUpdateComplete(): Promise<boolean> {
const complete = await super.getUpdateComplete();

const tabs = [...this.children] as Tab[];
const tabUpdateCompletes = tabs.map((tab) => {
if (typeof tab.updateComplete !== 'undefined') {
return tab.updateComplete;
}
return Promise.resolve(true);
});
Promise.all(tabUpdateCompletes).then(() => super.manageAutoFocus());

await Promise.all(tabUpdateCompletes);
return complete;
}

public async scrollToSelection(): Promise<void> {
if (!this.enableTabsScroll || !this.selected) {
return;
}

await this.updateComplete;
const selectedTab = this.tabs.find(
(tab) => tab.value === this.selected
);
selectedTab?.scrollIntoView();
}

protected override updated(
changedProperties: PropertyValueMap<this>
): void {
super.updated(changedProperties);

if (changedProperties.has('selected')) {
this.scrollToSelection();
}
}

protected managePanels({
Expand Down
7 changes: 7 additions & 0 deletions packages/tabs/src/tab.css
Expand Up @@ -12,6 +12,13 @@ governing permissions and limitations under the License.

@import url('./spectrum-tab.css');

:host {
scroll-margin-inline: var(
--mod-tabs-item-horizontal-spacing,
var(--spectrum-tabs-item-horizontal-spacing)
);
}

:host([disabled]) {
pointer-events: none;
}
Expand Down
4 changes: 3 additions & 1 deletion packages/tabs/stories/index.ts
Expand Up @@ -18,13 +18,15 @@ import { html, nothing, TemplateResult } from '@spectrum-web-components/base';
import { repeat } from '@spectrum-web-components/base/src/directives.js';

export interface OverflowProperties {
selected?: number;
count?: number;
size?: string;
includeTabPanel?: boolean;
compact?: boolean;
}

export const renderTabsOverflowExample = ({
selected = 1,
count = 20,
size = 'm',
includeTabPanel,
Expand All @@ -41,7 +43,7 @@ export const renderTabsOverflowExample = ({
</style>
<div class="container">
<sp-tabs-overflow size=${size} ?compact=${compact}>
<sp-tabs size=${size} selected="1" ?compact=${compact}>
<sp-tabs size=${size} selected=${selected} ?compact=${compact}>
${repeat(
new Array(count),
(item) => item,
Expand Down
7 changes: 7 additions & 0 deletions packages/tabs/stories/tabs-overflow.stories.ts
Expand Up @@ -23,3 +23,10 @@ export const compact = (args: OverflowProperties): TemplateResult => {
compact.args = {
compact: true,
};

export const autoscroll = (args: OverflowProperties): TemplateResult => {
return renderTabsOverflowExample(args);
};
autoscroll.args = {
selected: 15,
};
82 changes: 69 additions & 13 deletions packages/tabs/test/tabs-overflow.test.ts
Expand Up @@ -16,23 +16,38 @@ import '@spectrum-web-components/tabs/sp-tab.js';
import '@spectrum-web-components/tabs/sp-tabs.js';
import '@spectrum-web-components/tabs/sp-tab-panel.js';
import '@spectrum-web-components/tabs/sp-tabs-overflow.js';
import { Tab, TabsOverflow } from '@spectrum-web-components/tabs';
import { Tab, Tabs, TabsOverflow } from '@spectrum-web-components/tabs';
import { ActionButton } from '@spectrum-web-components/action-button';

import { elementUpdated, expect, fixture } from '@open-wc/testing';
import { html, nothing } from '@spectrum-web-components/base';
import {
ElementSize,
ElementSizes,
html,
nothing,
} from '@spectrum-web-components/base';
import { repeat } from 'lit/directives/repeat.js';

const renderTabsOverflow = async (
count: number,
size: string,
includeTabPanel: boolean
): Promise<HTMLDivElement> => {
type OverflowProperties = {
count: number;
size: ElementSize;
includeTabPanel: boolean;
selected?: number;
autoscroll?: boolean;
};

const renderTabsOverflow = async ({
count,
size,
includeTabPanel,
selected = 1,
autoscroll = false,
}: OverflowProperties): Promise<HTMLDivElement> => {
const tabsContainer = await fixture<HTMLDivElement>(
html`
<div class="container" style="width: 200px; height: 150px;">
<sp-tabs-overflow>
<sp-tabs size=${size} selected="1" enableTabsScroll=${true}>
<sp-tabs-overflow ?autoscroll=${autoscroll}>
<sp-tabs size=${size} selected=${selected}>
${repeat(
new Array(count),
(item) => item,
Expand Down Expand Up @@ -73,7 +88,7 @@ describe('TabsOverflow', () => {
const el = await fixture<TabsOverflow>(
html`
<sp-tabs-overflow>
<sp-tabs size="m" selected="1" enableTabsScroll=${true}>
<sp-tabs size="m" selected="1">
<sp-tab label="Tab Item 1" value="1"></sp-tab>
<sp-tab label="Tab Item 2" value="2"></sp-tab>
<sp-tab-panel value="1">Tab Content 1</sp-tab-panel>
Expand All @@ -89,7 +104,11 @@ describe('TabsOverflow', () => {
});

it('show render left and right buttons in shadowDom', async () => {
const el = await renderTabsOverflow(20, 'l', true);
const el = await renderTabsOverflow({
count: 20,
size: ElementSizes.L,
includeTabPanel: true,
});

const spTabsOverflows: TabsOverflow = el.querySelector(
'sp-tabs-overflow'
Expand All @@ -105,7 +124,11 @@ describe('TabsOverflow', () => {
});

it('reflect proper sp-tab size', async () => {
const el = await renderTabsOverflow(20, 'm', true);
const el = await renderTabsOverflow({
count: 20,
size: ElementSizes.M,
includeTabPanel: true,
});

const spTabsOverflows: TabsOverflow = el.querySelector(
'sp-tabs-overflow'
Expand All @@ -115,7 +138,11 @@ describe('TabsOverflow', () => {
});

it('should scroll when the button is clicked', async () => {
const el = await renderTabsOverflow(20, 'l', true);
const el = await renderTabsOverflow({
count: 20,
size: ElementSizes.L,
includeTabPanel: true,
});
await elementUpdated(el);

const spTabsOverflows: TabsOverflow = el.querySelector(
Expand Down Expand Up @@ -157,4 +184,33 @@ describe('TabsOverflow', () => {
const slotContent = slot?.assignedElements() || '';
expect(slotContent[0].toString()).to.not.contains('Tabs');
});

it('should automatically bring the selected tab into view', async () => {
const el = await renderTabsOverflow({
count: 20,
size: ElementSizes.L,
includeTabPanel: false,
selected: 10,
});
await elementUpdated(el);

// Grab the list of tabs.
const tabsEl = el.querySelector('sp-tabs') as Tabs;

// Grab the coordonates of the selected tab.
const selectedTab = tabsEl.querySelector(
`[role="tab"][value="10"]`
) as Tab;
expect(selectedTab).to.exist;
const selectedTabPosition = selectedTab.getBoundingClientRect();

// Selected tab is in the viewport, offset left is greater than 0 and less than the width of the tabs.
expect(selectedTabPosition.left).to.be.greaterThan(0);
expect(selectedTabPosition.left).to.be.lessThan(tabsEl.clientWidth);

// First tab is not in the viewport anymore, its offset left is less than 0.
const firstTab = tabsEl.querySelector(`[role="tab"][value="1"]`) as Tab;
const firstTabPosition = firstTab.getBoundingClientRect();
expect(firstTabPosition.left).to.be.lessThan(0);
});
});

0 comments on commit a187057

Please sign in to comment.