From 61c53619e6f5629054c4325c3cb106f17e767bbe Mon Sep 17 00:00:00 2001 From: Philip Peterson Date: Sun, 13 Apr 2025 14:07:29 -0700 Subject: [PATCH] Scrollable tabs --- lib/components/tab.tsx | 17 ++++++++++++++--- lib/components/tabs.tsx | 35 ++++++++++++++++++++++++++++++++--- typings/hyper.d.ts | 1 + 3 files changed, 47 insertions(+), 6 deletions(-) diff --git a/lib/components/tab.tsx b/lib/components/tab.tsx index c977c039..e52d815f 100644 --- a/lib/components/tab.tsx +++ b/lib/components/tab.tsx @@ -1,8 +1,8 @@ -import React, {forwardRef} from 'react'; +import React, {useEffect, useRef} from 'react'; import type {TabProps} from '../../typings/hyper'; -const Tab = forwardRef((props, ref) => { +const Tab = (props: TabProps) => { const handleClick = (event: React.MouseEvent) => { const isLeftClick = event.nativeEvent.which === 1; @@ -19,6 +19,16 @@ const Tab = forwardRef((props, ref) => { } }; + const ref = useRef(null); + + useEffect(() => { + if (props.lastFocused) { + ref?.current?.scrollIntoView({ + behavior: 'smooth' + }); + } + }, [props.lastFocused]); + const {isActive, isFirst, isLast, borderColor, hasActivity} = props; return ( @@ -60,6 +70,7 @@ const Tab = forwardRef((props, ref) => { list-style-type: none; flex-grow: 1; position: relative; + min-width: 10em; } .tab_tab:hover { @@ -161,7 +172,7 @@ const Tab = forwardRef((props, ref) => { `} ); -}); +}; Tab.displayName = 'Tab'; diff --git a/lib/components/tabs.tsx b/lib/components/tabs.tsx index 49043e7d..5aa880c0 100644 --- a/lib/components/tabs.tsx +++ b/lib/components/tabs.tsx @@ -1,6 +1,8 @@ -import React, {forwardRef} from 'react'; +import React, {forwardRef, useEffect, useState} from 'react'; -import type {TabsProps} from '../../typings/hyper'; +import debounce from 'lodash/debounce'; + +import type {ITab, TabsProps} from '../../typings/hyper'; import {decorate, getTabProps} from '../utils/plugins'; import DropdownButton from './new-tab'; @@ -12,6 +14,23 @@ const isMac = /Mac/.test(navigator.userAgent); const Tabs = forwardRef((props, ref) => { const {tabs = [], borderColor, onChange, onClose, fullScreen} = props; + const [shouldFocusCounter, setShouldFocusCounter] = useState({ + index: 0, + when: undefined as Date | undefined + }); + + const scrollToActiveTab = debounce((currTabs: ITab[]) => { + const activeTab = currTabs.findIndex((t) => t.isActive); + setShouldFocusCounter({ + index: activeTab, + when: new Date() + }); + }, 100); + + useEffect(() => { + scrollToActiveTab(tabs); + }, [tabs, tabs.length]); + const hide = !isMac && tabs.length === 1; return ( @@ -31,8 +50,12 @@ const Tabs = forwardRef((props, ref) => { isActive, hasActivity, onSelect: onChange.bind(null, uid), - onClose: onClose.bind(null, uid) + onClose: onClose.bind(null, uid), + lastFocused: undefined as Date | undefined }); + if (shouldFocusCounter.index === i) { + tabProps.lastFocused = shouldFocusCounter.when; + } return ; })} @@ -85,6 +108,12 @@ const Tabs = forwardRef((props, ref) => { flex-flow: row; margin-left: ${isMac ? '76px' : '0'}; flex-grow: 1; + overflow-x: auto; + } + + .tabs_list::-webkit-scrollbar, + .tabs_list::-webkit-scrollbar-button { + display: none; } .tabs_fullScreen { diff --git a/typings/hyper.d.ts b/typings/hyper.d.ts index 4ba77c54..55f7b484 100644 --- a/typings/hyper.d.ts +++ b/typings/hyper.d.ts @@ -230,6 +230,7 @@ export type TabProps = { onClose: () => void; onSelect: () => void; text: string; + lastFocused: Date | undefined; } & extensionProps; export type ITab = {