Skip to content

Commit 5f0914b

Browse files
macandcheesegithub-actions[bot]
authored andcommitted
fix(tabs): Update tab title indicator display (#9666)
**Related Issue:** #8800, #8772 ## Summary Removes the previous selection indicator behavior and moves the styling to the Tab Title component itself. Removes associated stale code and tests. Small visual tweaks to resolve alignment and spacing issues.
1 parent 326001c commit 5f0914b

File tree

12 files changed

+304
-314
lines changed

12 files changed

+304
-314
lines changed

packages/calcite-components/src/components.d.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4873,8 +4873,6 @@ export namespace Components {
48734873
}
48744874
interface CalciteTabNav {
48754875
"bordered": boolean;
4876-
"indicatorOffset": number;
4877-
"indicatorWidth": number;
48784876
"layout": TabLayout;
48794877
/**
48804878
* Use this property to override individual strings used by the component.
@@ -12878,8 +12876,6 @@ declare namespace LocalJSX {
1287812876
}
1287912877
interface CalciteTabNav {
1288012878
"bordered"?: boolean;
12881-
"indicatorOffset"?: number;
12882-
"indicatorWidth"?: number;
1288312879
"layout"?: TabLayout;
1288412880
/**
1288512881
* Use this property to override individual strings used by the component.

packages/calcite-components/src/components/tab-nav/resources.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ export const ICON = {
44
} as const;
55

66
export const CSS = {
7-
activeIndicatorContainer: "tab-nav-active-indicator-container",
87
container: "tab-nav",
98
containerHasEndTabTitleOverflow: "tab-nav--end-overflow",
109
containerHasStartTabTitleOverflow: "tab-nav--start-overflow",

packages/calcite-components/src/components/tab-nav/tab-nav.e2e.ts

Lines changed: 0 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -48,48 +48,6 @@ describe("calcite-tab-nav", () => {
4848
expect(activeEventSpy).toHaveReceivedEventTimes(2);
4949
});
5050

51-
describe("selected indicator", () => {
52-
const tabTitles = html`
53-
<calcite-tab-title selected>Tab 1 Title</calcite-tab-title>
54-
<calcite-tab-title>Tab 2 Title</calcite-tab-title>
55-
<calcite-tab-title>Tab 3 Title</calcite-tab-title>
56-
<calcite-tab-title>Tab 4 Title</calcite-tab-title>
57-
`;
58-
59-
it("has its active indicator positioned from left if LTR", async () => {
60-
const page = await newE2EPage();
61-
await page.setContent(`<calcite-tab-nav>${tabTitles}</calcite-tab-nav>`);
62-
const element = await page.find("calcite-tab-nav >>> .tab-nav-active-indicator");
63-
const style = await element.getComputedStyle();
64-
expect(style["left"]).toBe("0px");
65-
expect(style["right"]).not.toBe("0px");
66-
expect(style["width"]).not.toBe("0px");
67-
});
68-
69-
it("has its active indicator positioned from right if RTL", async () => {
70-
const page = await newE2EPage();
71-
await page.setContent(`<calcite-tab-nav dir='rtl'>${tabTitles}</calcite-tab-nav>`);
72-
const element = await page.find("calcite-tab-nav >>> .tab-nav-active-indicator");
73-
const style = await element.getComputedStyle();
74-
expect(style["right"]).toBe("0px");
75-
expect(style["left"]).not.toBe("0px");
76-
expect(style["width"]).not.toBe("0px");
77-
});
78-
79-
it("updates position when made visible", async () => {
80-
const page = await newE2EPage();
81-
await page.setContent(`<calcite-tab-nav hidden>${tabTitles}</calcite-tab-nav>`);
82-
const tabNav = await page.find("calcite-tab-nav");
83-
const indicator = await page.find("calcite-tab-nav >>> .tab-nav-active-indicator");
84-
85-
tabNav.setProperty("hidden", false);
86-
await page.waitForChanges();
87-
88-
const style = await indicator.getComputedStyle();
89-
expect(style["width"]).not.toBe("0px");
90-
});
91-
});
92-
9351
it("focuses on keyboard interaction", async () => {
9452
const page = await newE2EPage();
9553
await page.setContent(

packages/calcite-components/src/components/tab-nav/tab-nav.scss

Lines changed: 0 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@
2828
$last-mask-color-stop-position: 51%; // we go beyond the half point to ensure the mask color stops overlap when both start and end are overflowing
2929

3030
.tab-nav--start-overflow {
31-
.tab-nav-active-indicator-container,
3231
.tab-titles-slot-wrapper {
3332
mask-image: linear-gradient(
3433
to var(--calcite-internal-tab-nav-gradient-end-side),
@@ -41,7 +40,6 @@ $last-mask-color-stop-position: 51%; // we go beyond the half point to ensure th
4140
}
4241

4342
.tab-nav--end-overflow {
44-
.tab-nav-active-indicator-container,
4543
.tab-titles-slot-wrapper {
4644
mask-image: linear-gradient(
4745
to var(--calcite-internal-tab-nav-gradient-start-side),
@@ -54,7 +52,6 @@ $last-mask-color-stop-position: 51%; // we go beyond the half point to ensure th
5452
}
5553

5654
.tab-nav--start-overflow.tab-nav--end-overflow {
57-
.tab-nav-active-indicator-container,
5855
.tab-titles-slot-wrapper {
5956
mask-image: linear-gradient(
6057
to var(--calcite-internal-tab-nav-gradient-end-side),
@@ -126,26 +123,6 @@ $last-mask-color-stop-position: 51%; // we go beyond the half point to ensure th
126123
overflow-hidden;
127124
}
128125

129-
// prevent indicator overflow in horizontal scrolling situations
130-
.tab-nav-active-indicator-container {
131-
@apply absolute
132-
bottom-0
133-
h-0.5
134-
inset-x-0
135-
overflow-hidden
136-
w-full;
137-
}
138-
139-
.tab-nav-active-indicator {
140-
@apply absolute
141-
bg-brand
142-
bottom-0
143-
block
144-
h-0.5
145-
ease-out
146-
transition-all;
147-
}
148-
149126
.scroll-button-container {
150127
@apply absolute bottom-0 top-0;
151128

@@ -195,29 +172,4 @@ $last-mask-color-stop-position: 51%; // we go beyond the half point to ensure th
195172
}
196173
}
197174

198-
:host .position-bottom .tab-nav-active-indicator {
199-
inset-block-end: unset;
200-
@apply top-0;
201-
}
202-
203-
:host .position-bottom .tab-nav-active-indicator-container {
204-
inset-block-end: unset;
205-
@apply top-0;
206-
}
207-
208-
:host([bordered]) .tab-nav-active-indicator-container {
209-
inset-block-end: unset; // display active blue line above instead of below
210-
}
211-
212-
:host([bordered]) .position-bottom .tab-nav-active-indicator-container {
213-
inset-block-end: 0; // display active blue line below instead of above
214-
inset-block-start: unset;
215-
}
216-
217-
@media (forced-colors: active) {
218-
.tab-nav-active-indicator {
219-
background-color: highlight;
220-
}
221-
}
222-
223175
@include base-component();

packages/calcite-components/src/components/tab-nav/tab-nav.tsx

Lines changed: 0 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,6 @@ export class TabNav implements LocalizedComponent, T9nComponent {
7979
this.calciteInternalTabChange.emit({
8080
tab: this.selectedTabId,
8181
});
82-
this.updateActiveIndicator();
8382
}
8483

8584
/**
@@ -106,16 +105,6 @@ export class TabNav implements LocalizedComponent, T9nComponent {
106105
*/
107106
@Prop({ reflect: true, mutable: true }) bordered = false;
108107

109-
/**
110-
* @internal
111-
*/
112-
@Prop({ mutable: true }) indicatorOffset: number;
113-
114-
/**
115-
* @internal
116-
*/
117-
@Prop({ mutable: true }) indicatorWidth: number;
118-
119108
/**
120109
* Made into a prop for testing purposes only.
121110
*
@@ -167,10 +156,6 @@ export class TabNav implements LocalizedComponent, T9nComponent {
167156
this.layout = parentTabsEl?.layout;
168157
this.bordered = parentTabsEl?.bordered;
169158
this.effectiveDir = getElementDir(this.el);
170-
171-
if (this.selectedTitle) {
172-
this.updateActiveIndicator();
173-
}
174159
}
175160

176161
componentDidRender(): void {
@@ -201,11 +186,6 @@ export class TabNav implements LocalizedComponent, T9nComponent {
201186
//--------------------------------------------------------------------------
202187

203188
render(): VNode {
204-
const width = `${this.indicatorWidth}px`;
205-
const offset = `${this.indicatorOffset}px`;
206-
const indicatorStyle =
207-
this.effectiveDir !== "rtl" ? { width, left: offset } : { width, right: offset };
208-
209189
return (
210190
<Host role="tablist">
211191
<div
@@ -217,7 +197,6 @@ export class TabNav implements LocalizedComponent, T9nComponent {
217197
[`position-${this.position}`]: true,
218198
[CSS_UTILITY.rtl]: this.effectiveDir === "rtl",
219199
}}
220-
ref={this.storeContainerRef}
221200
>
222201
{this.renderScrollButton("start")}
223202
<div
@@ -230,18 +209,6 @@ export class TabNav implements LocalizedComponent, T9nComponent {
230209
>
231210
<slot onSlotchange={this.onSlotChange} />
232211
</div>
233-
<div
234-
class={{
235-
[CSS.activeIndicatorContainer]: true,
236-
}}
237-
ref={(el) => (this.activeIndicatorContainerEl = el)}
238-
>
239-
<div
240-
class="tab-nav-active-indicator"
241-
ref={(el) => (this.activeIndicatorEl = el as HTMLElement)}
242-
style={indicatorStyle}
243-
/>
244-
</div>
245212
{this.renderScrollButton("end")}
246213
</div>
247214
</Host>
@@ -367,11 +334,6 @@ export class TabNav implements LocalizedComponent, T9nComponent {
367334
event.stopPropagation();
368335
}
369336

370-
@Listen("calciteInternalTabIconChanged")
371-
iconStartChangeHandler(): void {
372-
this.updateActiveIndicator();
373-
}
374-
375337
//--------------------------------------------------------------------------
376338
//
377339
// Events
@@ -432,12 +394,6 @@ export class TabNav implements LocalizedComponent, T9nComponent {
432394
});
433395
}
434396

435-
private activeIndicatorEl: HTMLElement;
436-
437-
private activeIndicatorContainerEl: HTMLDivElement;
438-
439-
private containerEl: HTMLDivElement;
440-
441397
private effectiveDir: Direction = "ltr";
442398

443399
private lastScrollWheelAxis: "x" | "y" = "x";
@@ -450,12 +406,6 @@ export class TabNav implements LocalizedComponent, T9nComponent {
450406

451407
private resizeObserver = createObserver("resize", () => {
452408
this.updateScrollingState();
453-
454-
if (!this.activeIndicatorEl) {
455-
return;
456-
}
457-
458-
this.updateActiveIndicator();
459409
});
460410

461411
private get scrollerButtonWidth(): number {
@@ -469,20 +419,6 @@ export class TabNav implements LocalizedComponent, T9nComponent {
469419
//
470420
//--------------------------------------------------------------------------
471421

472-
private updateActiveIndicator(): void {
473-
const tabTitleScrollLeft = this.tabTitleContainerEl?.scrollLeft;
474-
const containerScrollLeft = this.containerEl?.scrollLeft;
475-
const navWidth = this.activeIndicatorContainerEl?.offsetWidth;
476-
const tabLeft = this.selectedTitle?.offsetLeft;
477-
const tabWidth = this.selectedTitle?.offsetWidth;
478-
const offsetRight = navWidth - tabLeft - tabWidth;
479-
const offsetBase = this.effectiveDir === "ltr" ? tabLeft : offsetRight;
480-
const multiplier = this.effectiveDir === "ltr" ? -1 : 1;
481-
482-
this.indicatorOffset = offsetBase + multiplier * (containerScrollLeft + tabTitleScrollLeft);
483-
this.indicatorWidth = this.selectedTitle?.offsetWidth;
484-
}
485-
486422
private onTabTitleWheel = (event: WheelEvent): void => {
487423
event.preventDefault();
488424

@@ -504,7 +440,6 @@ export class TabNav implements LocalizedComponent, T9nComponent {
504440

505441
const scrollByX = (this.effectiveDir === "rtl" ? -1 : 1) * scrollBy;
506442
(event.currentTarget as HTMLDivElement).scrollBy(scrollByX, 0);
507-
requestAnimationFrame(() => this.updateActiveIndicator());
508443
};
509444

510445
private onSlotChange = (event: Event): void => {
@@ -517,8 +452,6 @@ export class TabNav implements LocalizedComponent, T9nComponent {
517452
this.calciteInternalTabNavSlotChange.emit(slottedElements);
518453
};
519454

520-
private storeContainerRef = (el: HTMLDivElement) => (this.containerEl = el);
521-
522455
private storeTabTitleWrapperRef = (el: HTMLDivElement) => {
523456
this.tabTitleContainerEl = el;
524457
this.intersectionObserver = createObserver("intersection", () => this.updateScrollingState(), {
@@ -646,7 +579,6 @@ export class TabNav implements LocalizedComponent, T9nComponent {
646579
}
647580

648581
private onTabTitleScroll = (): void => {
649-
this.updateActiveIndicator();
650582
this.updateScrollingState();
651583
};
652584

@@ -699,7 +631,6 @@ export class TabNav implements LocalizedComponent, T9nComponent {
699631
}
700632

701633
requestAnimationFrame(() => {
702-
this.updateActiveIndicator();
703634
tabTitles[this.selectedTabId].focus();
704635
});
705636
}

packages/calcite-components/src/components/tab-title/resources.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
1+
import { Scale } from "../interfaces";
2+
13
export const CSS = {
24
closeButton: "close-button",
35
container: "container",
6+
containerBottom: "container--bottom",
47
content: "content",
58
contentHasText: "content--has-text",
69
iconEnd: "icon-end",
710
iconPresent: "icon-present",
811
iconStart: "icon-start",
912
titleIcon: "calcite-tab-title--icon",
13+
scale: (scale: Scale) => `scale-${scale}` as const,
14+
selectedIndicator: "selected-indicator",
1015
};
1116

1217
export const ICONS = {

packages/calcite-components/src/components/tab-title/tab-title.e2e.ts

Lines changed: 0 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -343,54 +343,4 @@ describe("calcite-tab-title", () => {
343343
await page.keyboard.press("Enter");
344344
expect(activeEventSpy).toHaveReceivedEventTimes(2);
345345
});
346-
347-
describe("when the active tab-title changes", () => {
348-
it("should move the active tab nav indicator", async () => {
349-
const page = await newE2EPage({
350-
html: `
351-
<calcite-tabs>
352-
<calcite-tab-nav slot="title-group">
353-
<calcite-tab-title class="title-1">Tab 1 Title</calcite-tab-title>
354-
<calcite-tab-title class="title-2" selected>Tab 2 Title</calcite-tab-title>
355-
<calcite-tab-title>Tab 3 Title</calcite-tab-title>
356-
<calcite-tab-title>Tab 4 Title</calcite-tab-title>
357-
</calcite-tab-nav>
358-
<calcite-tab>Tab 1 Content</calcite-tab>
359-
<calcite-tab selected>Tab 2 Content</calcite-tab>
360-
<calcite-tab>Tab 3 Content</calcite-tab>
361-
<calcite-tab>Tab 4 Content</calcite-tab>
362-
</calcite-tabs>
363-
`,
364-
});
365-
const tabTitle1 = await page.find(".title-1");
366-
const tabTitle2 = await page.find(".title-2");
367-
368-
expect(await (await page.find("calcite-tab-title[selected]")).innerText).toEqual("Tab 2 Title");
369-
expect(
370-
await page.evaluate(() => {
371-
return (
372-
document
373-
.querySelector("calcite-tab-nav")
374-
.shadowRoot.querySelector(".tab-nav-active-indicator") as HTMLDivElement
375-
).style.left;
376-
}),
377-
).not.toEqual("0px");
378-
379-
// toggle new selected tab-title
380-
await tabTitle2.removeAttribute("selected");
381-
await tabTitle1.setAttribute("selected", true);
382-
await page.waitForChanges();
383-
384-
expect(await (await page.find("calcite-tab-title[selected]")).innerText).toEqual("Tab 1 Title");
385-
expect(
386-
await page.evaluate(() => {
387-
return (
388-
document
389-
.querySelector("calcite-tab-nav")
390-
.shadowRoot.querySelector(".tab-nav-active-indicator") as HTMLDivElement
391-
).style.left;
392-
}),
393-
).toEqual("0px");
394-
});
395-
});
396346
});

0 commit comments

Comments
 (0)