Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 50b2fde

Browse files
committedMar 27, 2022
Implement tab scrolling on tabbar
1 parent ddf4ae5 commit 50b2fde

File tree

3 files changed

+444
-26
lines changed

3 files changed

+444
-26
lines changed
 

‎packages/widgets/src/dockpanel.ts

+29
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,9 @@ export class DockPanel extends Widget {
5757
if (options.addButtonEnabled !== undefined) {
5858
this._addButtonEnabled = options.addButtonEnabled;
5959
}
60+
if (options.tabScrollingEnabled !== undefined) {
61+
this._tabScrollingEnabled = options.tabScrollingEnabled;
62+
}
6063

6164
// Toggle the CSS mode attribute.
6265
this.dataset['mode'] = this._mode;
@@ -258,6 +261,23 @@ export class DockPanel extends Widget {
258261
});
259262
}
260263

264+
/**
265+
* Whether scrolling of tabs in tab bars is enabled.
266+
*/
267+
get tabScrollingEnabled(): boolean {
268+
return this._tabScrollingEnabled;
269+
}
270+
271+
/**
272+
* Set whether the add buttons for each tab bar are enabled.
273+
*/
274+
set tabScrollingEnabled(value: boolean) {
275+
this._tabScrollingEnabled = value;
276+
each(this.tabBars(), tabbar => {
277+
tabbar.scrollingEnabled = value;
278+
});
279+
}
280+
261281
/**
262282
* Whether the dock panel is empty.
263283
*/
@@ -938,6 +958,7 @@ export class DockPanel extends Widget {
938958
tabBar.tabsMovable = this._tabsMovable;
939959
tabBar.allowDeselect = false;
940960
tabBar.addButtonEnabled = this._addButtonEnabled;
961+
tabBar.scrollingEnabled = this._tabScrollingEnabled;
941962
tabBar.removeBehavior = 'select-previous-tab';
942963
tabBar.insertBehavior = 'select-tab-if-needed';
943964

@@ -1083,6 +1104,7 @@ export class DockPanel extends Widget {
10831104
private _tabsMovable: boolean = true;
10841105
private _tabsConstrained: boolean = false;
10851106
private _addButtonEnabled: boolean = false;
1107+
private _tabScrollingEnabled: boolean = false;
10861108
private _pressData: Private.IPressData | null = null;
10871109
private _layoutModified = new Signal<this, void>(this);
10881110

@@ -1165,6 +1187,13 @@ export namespace DockPanel {
11651187
* The default is `'false'`.
11661188
*/
11671189
addButtonEnabled?: boolean;
1190+
1191+
/**
1192+
* Enable scrolling in each of the dock panel's tab bars.
1193+
*
1194+
* The default is `'false'`.
1195+
*/
1196+
tabScrollingEnabled?: boolean;
11681197
}
11691198

11701199
/**

‎packages/widgets/src/tabbar.ts

+287-22
Original file line numberDiff line numberDiff line change
@@ -353,6 +353,28 @@ export class TabBar<T> extends Widget {
353353
}
354354
}
355355

356+
/**
357+
* Whether scrolling is enabled.
358+
*/
359+
get scrollingEnabled(): boolean {
360+
return this._scrollingEnabled;
361+
}
362+
363+
set scrollingEnabled(value: boolean) {
364+
// Do nothing if the value does not change.
365+
if (this._scrollingEnabled === value) {
366+
return;
367+
}
368+
369+
this._scrollingEnabled = value;
370+
if (value) {
371+
this.node.classList.add('lm-mod-scrollable');
372+
} else {
373+
this.node.classList.add('lm-mod-scrollable');
374+
}
375+
this.maybeSwitchScrollButtons();
376+
}
377+
356378
/**
357379
* A read-only array of the titles in the tab bar.
358380
*/
@@ -374,6 +396,20 @@ export class TabBar<T> extends Widget {
374396
)[0] as HTMLUListElement;
375397
}
376398

399+
/**
400+
* The tab bar content wrapper node.
401+
*
402+
* #### Notes
403+
* This is the node which the content node and enables scrolling.
404+
*
405+
* Modifying this node directly can lead to undefined behavior.
406+
*/
407+
get contentWrapperNode(): HTMLUListElement {
408+
return this.node.getElementsByClassName(
409+
'lm-TabBar-wrapper'
410+
)[0] as HTMLUListElement;
411+
}
412+
377413
/**
378414
* The tab bar add button node.
379415
*
@@ -388,6 +424,18 @@ export class TabBar<T> extends Widget {
388424
)[0] as HTMLDivElement;
389425
}
390426

427+
get scrollBeforeButtonNode(): HTMLDivElement {
428+
return this.node.getElementsByClassName(
429+
'lm-TabBar-scrollBeforeButton'
430+
)[0] as HTMLDivElement;
431+
}
432+
433+
get scrollAfterButtonNode(): HTMLDivElement {
434+
return this.node.getElementsByClassName(
435+
'lm-TabBar-scrollAfterButton'
436+
)[0] as HTMLDivElement;
437+
}
438+
391439
/**
392440
* Add a tab to the end of the tab bar.
393441
*
@@ -618,6 +666,9 @@ export class TabBar<T> extends Widget {
618666
event.preventDefault();
619667
event.stopPropagation();
620668
break;
669+
case 'scroll':
670+
this._evtScroll(event);
671+
break;
621672
}
622673
}
623674

@@ -628,6 +679,7 @@ export class TabBar<T> extends Widget {
628679
this.node.addEventListener('mousedown', this); // <DEPRECATED>
629680
this.node.addEventListener('pointerdown', this);
630681
this.node.addEventListener('dblclick', this);
682+
this.contentNode.addEventListener('scroll', this);
631683
}
632684

633685
/**
@@ -637,6 +689,7 @@ export class TabBar<T> extends Widget {
637689
this.node.removeEventListener('mousedown', this); // <DEPRECATED>
638690
this.node.removeEventListener('pointerdown', this);
639691
this.node.removeEventListener('dblclick', this);
692+
this.contentNode.removeEventListener('scroll', this);
640693
this._releaseMouse();
641694
}
642695

@@ -655,6 +708,80 @@ export class TabBar<T> extends Widget {
655708
content[i] = renderer.renderTab({ title, current, zIndex });
656709
}
657710
VirtualDOM.render(content, this.contentNode);
711+
this.maybeSwitchScrollButtons();
712+
}
713+
714+
protected onResize(msg: Widget.ResizeMessage): void {
715+
super.onResize(msg);
716+
this.maybeSwitchScrollButtons();
717+
}
718+
719+
protected maybeSwitchScrollButtons() {
720+
const scrollBefore = this.scrollBeforeButtonNode;
721+
const scrollAfter = this.scrollAfterButtonNode;
722+
const state = this._scrollState;
723+
724+
if (this.scrollingEnabled && state.totalSize > state.displayedSize) {
725+
// show both buttons
726+
scrollBefore.style.display = null;
727+
scrollAfter.style.display = null;
728+
} else {
729+
// hide both buttons
730+
scrollBefore.style.display = 'none';
731+
scrollAfter.style.display = 'none';
732+
}
733+
this.updateScrollingHints(state);
734+
}
735+
736+
/**
737+
* Adjust data reflecting the ability to scroll in each direction.
738+
*/
739+
protected updateScrollingHints(scrollState: Private.IScrollState) {
740+
const wrapper = this.contentWrapperNode;
741+
742+
if (!this.scrollingEnabled) {
743+
delete wrapper.dataset['canScroll'];
744+
return;
745+
}
746+
747+
const canScrollBefore = scrollState.position != 0;
748+
const canScrollAfter =
749+
scrollState.position != scrollState.totalSize - scrollState.displayedSize;
750+
751+
if (canScrollBefore && canScrollAfter) {
752+
wrapper.dataset['canScroll'] = 'both';
753+
} else if (canScrollBefore) {
754+
wrapper.dataset['canScroll'] = 'before';
755+
} else if (canScrollAfter) {
756+
wrapper.dataset['canScroll'] = 'after';
757+
} else {
758+
delete wrapper.dataset['canScroll'];
759+
}
760+
}
761+
762+
private get _scrollState(): Private.IScrollState {
763+
const content = this.contentNode;
764+
const contentRect = content.getBoundingClientRect();
765+
const isHorizontal = this.orientation === 'horizontal';
766+
const contentSize = isHorizontal ? contentRect.width : contentRect.height;
767+
const scrollTotal = isHorizontal
768+
? content.scrollWidth
769+
: content.scrollHeight;
770+
const scroll = Math.round(
771+
isHorizontal ? content.scrollLeft : content.scrollTop
772+
);
773+
return {
774+
displayedSize: Math.round(contentSize),
775+
totalSize: scrollTotal,
776+
position: scroll
777+
};
778+
}
779+
780+
/**
781+
* Handle the `'dblclick'` event for the tab bar.
782+
*/
783+
private _evtScroll(event: Event): void {
784+
this.updateScrollingHints(this._scrollState);
658785
}
659786

660787
/**
@@ -734,6 +861,52 @@ export class TabBar<T> extends Widget {
734861
}
735862
}
736863

864+
protected beginScrolling(direction: '-' | '+') {
865+
const initialRate = 5;
866+
const rateIncrease = 1;
867+
const maxRate = 20;
868+
const intervalHandle = setInterval(() => {
869+
if (!this._scrollData) {
870+
this.stopScrolling();
871+
return;
872+
}
873+
const rate = this._scrollData.rate;
874+
const direction = this._scrollData.scrollDirection;
875+
const change = (direction == '+' ? 1 : -1) * rate;
876+
if (this.orientation == 'horizontal') {
877+
this.contentNode.scrollLeft += change;
878+
} else {
879+
this.contentNode.scrollTop += change;
880+
}
881+
this._scrollData.rate = Math.min(
882+
this._scrollData.rate + rateIncrease,
883+
maxRate
884+
);
885+
const state = this._scrollState;
886+
if (
887+
(direction == '-' && state.position == 0) ||
888+
(direction == '+' &&
889+
state.totalSize == state.position + state.displayedSize)
890+
) {
891+
this.stopScrolling();
892+
}
893+
}, 50);
894+
this._scrollData = {
895+
timerHandle: intervalHandle,
896+
scrollDirection: direction,
897+
rate: initialRate
898+
};
899+
}
900+
901+
protected stopScrolling() {
902+
if (this._scrollData) {
903+
clearInterval(this._scrollData.timerHandle);
904+
}
905+
this._scrollData = null;
906+
const state = this._scrollState;
907+
this.updateScrollingHints(state);
908+
}
909+
737910
/**
738911
* Handle the `'mousedown'` event for the tab bar.
739912
*/
@@ -753,6 +926,19 @@ export class TabBar<T> extends Widget {
753926
this.addButtonEnabled &&
754927
this.addButtonNode.contains(event.target as HTMLElement);
755928

929+
let scrollBeforeButtonClicked =
930+
this.scrollingEnabled &&
931+
this.scrollBeforeButtonNode.contains(event.target as HTMLElement);
932+
933+
let scrollAfterButtonClicked =
934+
this.scrollingEnabled &&
935+
this.scrollAfterButtonNode.contains(event.target as HTMLElement);
936+
937+
const anyButtonClicked =
938+
addButtonClicked || scrollAfterButtonClicked || scrollBeforeButtonClicked;
939+
940+
const contentNode = this.contentNode;
941+
756942
// Lookup the tab nodes.
757943
let tabs = this.contentNode.children;
758944

@@ -761,8 +947,8 @@ export class TabBar<T> extends Widget {
761947
return ElementExt.hitTest(tab, event.clientX, event.clientY);
762948
});
763949

764-
// Do nothing if the press is not on a tab or the add button.
765-
if (index === -1 && !addButtonClicked) {
950+
// Do nothing if the press is not on a tab or any of the buttons.
951+
if (index === -1 && !anyButtonClicked) {
766952
return;
767953
}
768954

@@ -776,6 +962,10 @@ export class TabBar<T> extends Widget {
776962
index: index,
777963
pressX: event.clientX,
778964
pressY: event.clientY,
965+
initialScrollPosition:
966+
this.orientation == 'horizontal'
967+
? contentNode.scrollLeft
968+
: contentNode.scrollTop,
779969
tabPos: -1,
780970
tabSize: -1,
781971
tabPressPos: -1,
@@ -796,6 +986,10 @@ export class TabBar<T> extends Widget {
796986
if (event.button === 1 || addButtonClicked) {
797987
return;
798988
}
989+
if (scrollBeforeButtonClicked || scrollAfterButtonClicked) {
990+
this.beginScrolling(scrollBeforeButtonClicked ? '-' : '+');
991+
return;
992+
}
799993

800994
// Do nothing else if the close icon is clicked.
801995
let icon = tabs[index].querySelector(this.renderer.closeIconSelector);
@@ -902,8 +1096,24 @@ export class TabBar<T> extends Widget {
9021096
}
9031097
}
9041098

1099+
let overBeforeScrollButton =
1100+
this.scrollingEnabled &&
1101+
this.scrollBeforeButtonNode.contains(event.target as HTMLElement);
1102+
1103+
let overAfterScrollButton =
1104+
this.scrollingEnabled &&
1105+
this.scrollAfterButtonNode.contains(event.target as HTMLElement);
1106+
1107+
if (overBeforeScrollButton || overAfterScrollButton) {
1108+
// Start scrolling if the mouse is over scroll buttons
1109+
this.beginScrolling(overBeforeScrollButton ? '-' : '+');
1110+
} else {
1111+
// Stop scrolling if mouse is not over scroll buttons
1112+
this.stopScrolling();
1113+
}
1114+
9051115
// Update the positions of the tabs.
906-
Private.layoutTabs(tabs, data, event, this._orientation);
1116+
Private.layoutTabs(tabs, data, event, this._orientation, this._scrollState);
9071117
}
9081118

9091119
/**
@@ -938,6 +1148,10 @@ export class TabBar<T> extends Widget {
9381148
// Clear the drag data.
9391149
this._dragData = null;
9401150

1151+
if (this._scrollData) {
1152+
this.stopScrolling();
1153+
return;
1154+
}
9411155
// Handle clicking the add button.
9421156
let addButtonClicked =
9431157
this.addButtonEnabled &&
@@ -989,7 +1203,7 @@ export class TabBar<T> extends Widget {
9891203
}
9901204

9911205
// Position the tab at its final resting position.
992-
Private.finalizeTabPosition(data, this._orientation);
1206+
Private.finalizeTabPosition(data, this._orientation, this._scrollState);
9931207

9941208
// Remove the dragging class from the tab so it can be transitioned.
9951209
data.tab.classList.remove('lm-mod-dragging');
@@ -1241,7 +1455,9 @@ export class TabBar<T> extends Widget {
12411455
private _titlesEditable: boolean = false;
12421456
private _previousTitle: Title<T> | null = null;
12431457
private _dragData: Private.IDragData | null = null;
1458+
private _scrollData: Private.IScrollData | null = null;
12441459
private _addButtonEnabled: boolean = false;
1460+
private _scrollingEnabled: boolean = false;
12451461
private _tabMoved = new Signal<this, TabBar.ITabMovedArgs<T>>(this);
12461462
private _currentChanged = new Signal<this, TabBar.ICurrentChangedArgs<T>>(
12471463
this
@@ -1776,6 +1992,33 @@ namespace Private {
17761992
*/
17771993
export const DETACH_THRESHOLD = 20;
17781994

1995+
/**
1996+
* A struct which holds the scroll data for a tab bar.
1997+
*/
1998+
export interface IScrollData {
1999+
timerHandle: number;
2000+
scrollDirection: '+' | '-';
2001+
rate: number;
2002+
}
2003+
2004+
/**
2005+
* A struct which holds the scroll state for a tab bar.
2006+
*/
2007+
export interface IScrollState {
2008+
/**
2009+
* The size of the container where scrolling occurs (the visible part of the total).
2010+
*/
2011+
displayedSize: number;
2012+
/**
2013+
* The total size of the content to be scrolled.
2014+
*/
2015+
totalSize: number;
2016+
/**
2017+
* The current position (offset) of the scroll state.
2018+
*/
2019+
position: number;
2020+
}
2021+
17792022
/**
17802023
* A struct which holds the drag data for a tab bar.
17812024
*/
@@ -1795,6 +2038,11 @@ namespace Private {
17952038
*/
17962039
pressX: number;
17972040

2041+
/**
2042+
* The initial scroll position
2043+
*/
2044+
initialScrollPosition: number;
2045+
17982046
/**
17992047
* The mouse press client Y position.
18002048
*/
@@ -1890,16 +2138,37 @@ namespace Private {
18902138
*/
18912139
export function createNode(): HTMLDivElement {
18922140
let node = document.createElement('div');
2141+
let scrollBefore = document.createElement('div');
2142+
scrollBefore.className =
2143+
'lm-TabBar-button lm-TabBar-scrollButton lm-TabBar-scrollBeforeButton';
2144+
scrollBefore.style.display = 'none';
2145+
scrollBefore.setAttribute('role', 'button');
2146+
scrollBefore.innerText = '<';
2147+
let scrollAfter = document.createElement('div');
2148+
scrollAfter.className =
2149+
'lm-TabBar-button lm-TabBar-scrollButton lm-TabBar-scrollAfterButton';
2150+
scrollAfter.style.display = 'none';
2151+
scrollAfter.setAttribute('role', 'button');
2152+
scrollAfter.innerText = '>';
2153+
node.appendChild(scrollBefore);
2154+
18932155
let content = document.createElement('ul');
18942156
content.setAttribute('role', 'tablist');
18952157
content.className = 'lm-TabBar-content';
18962158
/* <DEPRECATED> */
18972159
content.classList.add('p-TabBar-content');
18982160
/* </DEPRECATED> */
1899-
node.appendChild(content);
2161+
2162+
let wrapper = document.createElement('div');
2163+
wrapper.className = 'lm-TabBar-wrapper';
2164+
wrapper.appendChild(content);
2165+
node.appendChild(wrapper);
2166+
2167+
node.appendChild(scrollAfter);
19002168

19012169
let add = document.createElement('div');
1902-
add.className = 'lm-TabBar-addButton lm-mod-hidden';
2170+
add.setAttribute('role', 'button');
2171+
add.className = 'lm-TabBar-button lm-TabBar-addButton lm-mod-hidden';
19032172
node.appendChild(add);
19042173
return node;
19052174
}
@@ -1976,23 +2245,24 @@ namespace Private {
19762245
tabs: HTMLCollection,
19772246
data: IDragData,
19782247
event: MouseEvent,
1979-
orientation: TabBar.Orientation
2248+
orientation: TabBar.Orientation,
2249+
scrollState: IScrollState
19802250
): void {
19812251
// Compute the orientation-sensitive values.
19822252
let pressPos: number;
19832253
let localPos: number;
19842254
let clientPos: number;
1985-
let clientSize: number;
2255+
const clientSize = scrollState.totalSize;
2256+
const scrollShift = scrollState.position - data.initialScrollPosition;
2257+
19862258
if (orientation === 'horizontal') {
1987-
pressPos = data.pressX;
1988-
localPos = event.clientX - data.contentRect!.left;
2259+
pressPos = data.pressX - scrollShift;
2260+
localPos = event.clientX - data.contentRect!.left + scrollState.position;
19892261
clientPos = event.clientX;
1990-
clientSize = data.contentRect!.width;
19912262
} else {
1992-
pressPos = data.pressY;
1993-
localPos = event.clientY - data.contentRect!.top;
2263+
pressPos = data.pressY - scrollShift;
2264+
localPos = event.clientY - data.contentRect!.top + scrollState.position;
19942265
clientPos = event.clientY;
1995-
clientSize = data.contentRect!.height;
19962266
}
19972267

19982268
// Compute the target data.
@@ -2034,15 +2304,10 @@ namespace Private {
20342304
*/
20352305
export function finalizeTabPosition(
20362306
data: IDragData,
2037-
orientation: TabBar.Orientation
2307+
orientation: TabBar.Orientation,
2308+
scrollState: IScrollState
20382309
): void {
2039-
// Compute the orientation-sensitive client size.
2040-
let clientSize: number;
2041-
if (orientation === 'horizontal') {
2042-
clientSize = data.contentRect!.width;
2043-
} else {
2044-
clientSize = data.contentRect!.height;
2045-
}
2310+
const clientSize = scrollState.totalSize;
20462311

20472312
// Compute the ideal final tab position.
20482313
let ideal: number;

‎packages/widgets/style/tabbar.css

+128-4
Original file line numberDiff line numberDiff line change
@@ -42,16 +42,16 @@
4242
}
4343

4444
/* <DEPRECATED> */
45-
.p-TabBar[data-orientation='horizontal'] > .p-TabBar-content,
45+
.p-TabBar[data-orientation='horizontal'] > .lm-TabBar-wrapper > .p-TabBar-content,
4646
/* </DEPRECATED> */
47-
.lm-TabBar[data-orientation='horizontal'] > .lm-TabBar-content {
47+
.lm-TabBar[data-orientation='horizontal'] > .lm-TabBar-wrapper > .lm-TabBar-content {
4848
flex-direction: row;
4949
}
5050

5151
/* <DEPRECATED> */
52-
.p-TabBar[data-orientation='vertical'] > .p-TabBar-content,
52+
.p-TabBar[data-orientation='vertical'] > .lm-TabBar-wrapper > .p-TabBar-content,
5353
/* </DEPRECATED> */
54-
.lm-TabBar[data-orientation='vertical'] > .lm-TabBar-content {
54+
.lm-TabBar[data-orientation='vertical'] > .lm-TabBar-wrapper > .lm-TabBar-content {
5555
flex-direction: column;
5656
}
5757

@@ -133,3 +133,127 @@
133133
box-sizing: border-box;
134134
background: inherit;
135135
}
136+
137+
.lm-TabBar-wrapper {
138+
/* Remove wrapper from DOM when scrolling is not enabled */
139+
display: contents;
140+
--lm-tabbar-scrollshadow-end: rgba(0, 0, 0, 0.3);
141+
position: relative;
142+
}
143+
144+
.lm-TabBar.lm-mod-scrollable .lm-TabBar-wrapper {
145+
/* Keep wrapper in DOM when scrolling is enabled */
146+
display: block;
147+
}
148+
149+
.lm-TabBar-content {
150+
scrollbar-width: none;
151+
}
152+
153+
.lm-TabBar-content::-webkit-scrollbar {
154+
display: none;
155+
}
156+
157+
.lm-TabBar[data-orientation='horizontal'].lm-mod-scrollable
158+
> .lm-TabBar-wrapper {
159+
overflow-x: hidden;
160+
overflow-y: hidden;
161+
}
162+
163+
.lm-TabBar[data-orientation='vertical'].lm-mod-scrollable > .lm-TabBar-wrapper {
164+
overflow-x: hidden;
165+
overflow-y: auto;
166+
}
167+
168+
.lm-TabBar > .lm-TabBar-wrapper > .lm-TabBar-content {
169+
width: 100%;
170+
}
171+
172+
.lm-TabBar[data-orientation='horizontal'].lm-mod-scrollable
173+
> .lm-TabBar-wrapper
174+
> .lm-TabBar-content {
175+
overflow-x: scroll;
176+
overflow-y: hidden;
177+
}
178+
179+
.lm-TabBar[data-orientation='vertical'].lm-mod-scrollable
180+
> .lm-TabBar-wrapper
181+
> .lm-TabBar-content {
182+
overflow-x: hidden;
183+
overflow-y: scroll;
184+
}
185+
186+
.lm-TabBar-wrapper::before,
187+
.lm-TabBar-wrapper::after {
188+
content: '';
189+
z-index: 10000;
190+
display: none;
191+
position: absolute;
192+
}
193+
194+
.lm-TabBar-wrapper[data-can-scroll='before']::before {
195+
display: block;
196+
}
197+
198+
.lm-TabBar-wrapper[data-can-scroll='after']::after {
199+
display: block;
200+
}
201+
202+
.lm-TabBar-wrapper[data-can-scroll='both']::before,
203+
.lm-TabBar-wrapper[data-can-scroll='both']::after {
204+
display: block;
205+
}
206+
207+
.lm-TabBar[data-orientation='horizontal'] > .lm-TabBar-wrapper::before,
208+
.lm-TabBar[data-orientation='horizontal'] > .lm-TabBar-wrapper::after {
209+
width: 5px;
210+
height: 100%;
211+
top: 0;
212+
}
213+
214+
.lm-TabBar[data-orientation='horizontal'] > .lm-TabBar-wrapper::before {
215+
background: linear-gradient(
216+
to left,
217+
rgba(0, 0, 0, 0),
218+
var(--lm-tabbar-scrollshadow-end)
219+
);
220+
left: 0;
221+
}
222+
223+
.lm-TabBar[data-orientation='horizontal'] > .lm-TabBar-wrapper::after {
224+
background: linear-gradient(
225+
to right,
226+
rgba(0, 0, 0, 0),
227+
var(--lm-tabbar-scrollshadow-end)
228+
);
229+
right: 0;
230+
}
231+
232+
.lm-TabBar[data-orientation='vertical'] > .lm-TabBar-wrapper::before,
233+
.lm-TabBar[data-orientation='vertical'] > .lm-TabBar-wrapper::after {
234+
height: 5px;
235+
width: 100%;
236+
left: 0;
237+
}
238+
239+
.lm-TabBar[data-orientation='vertical'] > .lm-TabBar-wrapper::before {
240+
background: linear-gradient(
241+
to top,
242+
rgba(0, 0, 0, 0),
243+
var(--lm-tabbar-scrollshadow-end)
244+
);
245+
top: 0;
246+
}
247+
248+
.lm-TabBar[data-orientation='vertical'] > .lm-TabBar-wrapper::after {
249+
background: linear-gradient(
250+
to bottom,
251+
rgba(0, 0, 0, 0),
252+
var(--lm-tabbar-scrollshadow-end)
253+
);
254+
bottom: 0;
255+
}
256+
257+
.lm-TabBar[data-orientation='vertical'] > .lm-TabBar-scrollButton {
258+
transform: rotate(90deg);
259+
}

0 commit comments

Comments
 (0)
Please sign in to comment.