ChannelTabs: Movable New Tab Button (#507)

* new tab button behavior

* lint horror

* fixed again
This commit is contained in:
Jake 2025-11-18 15:57:09 +00:00 • committed by GitHub
parent 4ac0a3e606
commit bcd450c0bd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 42 additions and 3 deletions

View file

@ -27,6 +27,7 @@ const cl = classNameFactory("vc-channeltabs-");
export default function ChannelsTabsContainer(props: BasicChannelTabsProps) {
const [userId, setUserId] = useState("");
const [tabsOverflow, setTabsOverflow] = useState(false);
const {
showBookmarkBar,
widerTabsAndBookmarks,
@ -57,7 +58,8 @@ export default function ChannelsTabsContainer(props: BasicChannelTabsProps) {
animationResizeHandle,
animationQuestsActive,
compactAutoExpandSelected,
compactAutoExpandOnHover
compactAutoExpandOnHover,
newTabButtonBehavior
} = settings.use([
"showBookmarkBar",
"widerTabsAndBookmarks",
@ -88,7 +90,8 @@ export default function ChannelsTabsContainer(props: BasicChannelTabsProps) {
"animationResizeHandle",
"animationQuestsActive",
"compactAutoExpandSelected",
"compactAutoExpandOnHover"
"compactAutoExpandOnHover",
"newTabButtonBehavior"
]);
const GhostTabs = useGhostTabs();
const isFullscreen = useStateFromStores([ChannelRTCStore], () => ChannelRTCStore.isFullscreenInContext() ?? false);
@ -100,6 +103,7 @@ export default function ChannelsTabsContainer(props: BasicChannelTabsProps) {
}, [userId]);
const ref = useRef<HTMLDivElement>(null);
const scrollerRef = useRef<HTMLDivElement>(null);
useEffect(() => {
setUpdaterFunction(update);
@ -128,6 +132,27 @@ export default function ChannelsTabsContainer(props: BasicChannelTabsProps) {
useEffect(() => {
_update();
}, [widerTabsAndBookmarks]);
useEffect(() => {
const scroller = scrollerRef.current;
if (!scroller) return;
const checkOverflow = () => {
if (!newTabButtonBehavior) {
setTabsOverflow(true);
return;
}
const overflow = scroller.scrollWidth > scroller.clientWidth;
setTabsOverflow(overflow);
};
checkOverflow();
const observer = new ResizeObserver(checkOverflow);
observer.observe(scroller);
return () => observer.disconnect();
}, [openedTabs.length, newTabButtonBehavior]);
useEffect(() => {
const matchesKeybind = (event: KeyboardEvent, keybindString: string): boolean => {
const parts = keybindString.split("+");
@ -295,7 +320,10 @@ export default function ChannelsTabsContainer(props: BasicChannelTabsProps) {
<div className={cl("separator")} />
</>}
<div className={cl("tab-container")}>
<HorizontalScroller className={cl("tab-scroller")}>
<HorizontalScroller
customRef={node => { scrollerRef.current = node; }}
className={cl("tab-scroller", newTabButtonBehavior && !tabsOverflow && "tab-scroller-following")}
>
{openedTabs.filter(tab => tab != null).map((tab, i) =>
<ChannelTab {...tab} index={i} key={tab.id} />
)}

View file

@ -107,6 +107,11 @@
padding-top: 1px;
}
.vc-channeltabs-tab-scroller-following {
width: fit-content;
flex: unset;
}
.vc-channeltabs-tab-scroller::-webkit-scrollbar {
display: none;
}

View file

@ -420,6 +420,12 @@ export const settings = definePluginSettings({
default: false,
restartNeeded: false
},
newTabButtonBehavior: {
type: OptionType.BOOLEAN,
description: "New tab (+) button follows tabs instead of staying pinned to the right",
default: true,
restartNeeded: false
},
oneTabPerServer: {
type: OptionType.BOOLEAN,
description: "Limit to one tab per server, so opening a new channel in that server will use the existing tab.",