Refactor Dashboard Tabs and Enhance CSS Styles
All checks were successful
farmcontrol/farmcontrol-ui/pipeline/head This commit looks good

- Updated CSS for dashboard tabs to improve layout and overflow handling, ensuring better visibility and usability.
- Replaced Typography's Text component with a custom ElipsisText component for improved text overflow management.
- Simplified tab selection logic in NavigationTabsContext, enhancing scrolling behavior when selecting tabs.
- Introduced a new utility function to scroll tabs into view, improving user experience during navigation.
This commit is contained in:
Tom Butcher 2026-09-21 01:58:03 +01:00
parent e7f3e2a6d4
commit d5096dbba3
3 changed files with 77 additions and 66 deletions

View File

@ -3640,6 +3640,7 @@ body.objectKanbanColumnResizing * {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
max-width: 240px;
overflow: visible; overflow: visible;
margin: 5px 0 0 0; margin: 5px 0 0 0;
padding: 0 10px 0 10px; padding: 0 10px 0 10px;
@ -3843,8 +3844,14 @@ body.objectKanbanColumnResizing * {
} }
.dashboard-tabs-label { .dashboard-tabs-label {
max-width: 280px; max-width: 186px;
min-width: 0; min-width: 0;
overflow: hidden;
}
.dashboard-tabs-label .elipsis-text-wrapper.ant-typography {
min-width: 0;
max-width: 100%;
} }
.dashboard-tab-item-container[data-sticky-placeholder='true'] .dashboard-tab-item-container[data-sticky-placeholder='true']

View File

@ -7,8 +7,9 @@ import {
useState, useState,
memo memo
} from 'react' } from 'react'
import { Button, Flex, Popover, Typography } from 'antd' import { Button, Flex, Popover } from 'antd'
import DashboardTabPreview from './DashboardTabPreview' import DashboardTabPreview from './DashboardTabPreview'
import ElipsisText from './ElipsisText'
import classNames from 'classnames' import classNames from 'classnames'
import ScrollBox from './ScrollBox' import ScrollBox from './ScrollBox'
import PlusIcon from '../../Icons/PlusIcon' import PlusIcon from '../../Icons/PlusIcon'
@ -23,8 +24,6 @@ import {
import { getDesktopWindowId } from '../../../electrobun-bridge.js' import { getDesktopWindowId } from '../../../electrobun-bridge.js'
import { hasExternalTabDrag, writeTabDragData } from './tabDrag' import { hasExternalTabDrag, writeTabDragData } from './tabDrag'
const { Text } = Typography
const DashboardTabChrome = () => ( const DashboardTabChrome = () => (
<> <>
<span className='dashboard-tab-item-outline-container' aria-hidden='true'> <span className='dashboard-tab-item-outline-container' aria-hidden='true'>
@ -49,9 +48,7 @@ const DashboardTabChrome = () => (
const DashboardTabLabel = ({ tab, onClose }) => ( const DashboardTabLabel = ({ tab, onClose }) => (
<Flex align='center' gap={6} className='dashboard-tabs-label'> <Flex align='center' gap={6} className='dashboard-tabs-label'>
<Text ellipsis style={{ maxWidth: 240 }}> <ElipsisText>{tab.title || 'Farm Control'}</ElipsisText>
{tab.title || 'Farm Control'}
</Text>
<span <span
className='dashboard-tabs-close' className='dashboard-tabs-close'
role='button' role='button'
@ -697,39 +694,10 @@ const DashboardTabs = () => {
? tabs.findIndex((tab) => tab.id === activeTabId) ? tabs.findIndex((tab) => tab.id === activeTabId)
: 0 : 0
const handleSelectTab = (tabId) => {
selectTab(tabId)
const scrollEl = rootRef.current?.querySelector(
'.simplebar-content-wrapper'
)
if (!scrollEl) return
const tabItem = document.getElementById(`dashboard-tab-item-${tabId}`)
if (!tabItem) return
const tabItemRect = tabItem.getBoundingClientRect()
const scrollRect = scrollEl.getBoundingClientRect()
const offset = 50
// Tab is too close to / off-screen on the left
if (tabItemRect.left < scrollRect.left + offset) {
scrollEl.scrollLeft -= scrollRect.left - tabItemRect.left + offset
}
// Tab is too close to / off-screen on the right
else if (tabItemRect.right > scrollRect.right - offset) {
scrollEl.scrollLeft += tabItemRect.right - scrollRect.right + offset
}
}
const tabItemProps = { const tabItemProps = {
onDragStart: handleItemDragStart, onDragStart: handleItemDragStart,
onDragEnd: handleItemDragEnd, onDragEnd: handleItemDragEnd,
onClick: handleSelectTab, onClick: selectTab,
onKeyDown: handleKeyDown, onKeyDown: handleKeyDown,
onDragOver: handleItemDragOver, onDragOver: handleItemDragOver,
onDrop: handleItemDrop, onDrop: handleItemDrop,

View File

@ -394,15 +394,51 @@ export const NavigationTabsProvider = ({ children }) => {
target: locationToEntry(entry), target: locationToEntry(entry),
from: locationToEntry(locationRef.current), from: locationToEntry(locationRef.current),
previousTabId: previousTabId:
previousTabId === undefined previousTabId === undefined ? activeTabIdRef.current : previousTabId
? activeTabIdRef.current
: previousTabId
} }
navigate(entryToPath(entry), { replace: true }) navigate(entryToPath(entry), { replace: true })
}, },
[navigate] [navigate]
) )
const registerTabStripRoot = useCallback((element) => {
tabStripRootRef.current = element
}, [])
const scrollTabIntoView = useCallback((tabId) => {
if (!tabId) return
const tabs = tabsRef.current
const isLastTab = tabs[tabs.length - 1]?.id === tabId
const leftInset = 24
const rightInset = isLastTab ? 72 : 28
const run = () => {
const root = tabStripRootRef.current
const scrollEl = root?.querySelector('.simplebar-content-wrapper')
if (!root || !scrollEl) return
const tabItem = root.querySelector(
`#dashboard-tab-item-${CSS.escape(String(tabId))}`
)
if (!tabItem) return
const tabRect = tabItem.getBoundingClientRect()
const scrollRect = scrollEl.getBoundingClientRect()
if (tabRect.left < scrollRect.left + leftInset) {
scrollEl.scrollLeft += tabRect.left - scrollRect.left - leftInset
} else if (tabRect.right > scrollRect.right - rightInset) {
scrollEl.scrollLeft += tabRect.right - scrollRect.right + rightInset
}
}
requestAnimationFrame(() => {
run()
requestAnimationFrame(run)
})
}, [])
const selectTab = useCallback( const selectTab = useCallback(
(tabId) => { (tabId) => {
if (!tabId || tabId === activeTabIdRef.current) return if (!tabId || tabId === activeTabIdRef.current) return
@ -414,6 +450,7 @@ export const NavigationTabsProvider = ({ children }) => {
markForeignLocation(previousTabId) markForeignLocation(previousTabId)
setActiveTabId(tabId) setActiveTabId(tabId)
activeTabIdRef.current = tabId activeTabIdRef.current = tabId
scrollTabIntoView(tabId)
if (entriesEqual(nextEntry, locationRef.current)) { if (entriesEqual(nextEntry, locationRef.current)) {
return return
@ -421,13 +458,9 @@ export const NavigationTabsProvider = ({ children }) => {
restoreToEntry(nextEntry, { previousTabId }) restoreToEntry(nextEntry, { previousTabId })
}, },
[markForeignLocation, restoreToEntry] [markForeignLocation, restoreToEntry, scrollTabIntoView]
) )
const registerTabStripRoot = useCallback((element) => {
tabStripRootRef.current = element
}, [])
useEffect(() => { useEffect(() => {
return () => { return () => {
if (scrollTabStripToEndIntervalRef.current) { if (scrollTabStripToEndIntervalRef.current) {
@ -537,10 +570,16 @@ export const NavigationTabsProvider = ({ children }) => {
if (!entriesEqual(nextEntry, locationRef.current)) { if (!entriesEqual(nextEntry, locationRef.current)) {
restoreToEntry(nextEntry, { previousTabId }) restoreToEntry(nextEntry, { previousTabId })
} }
scrollTabIntoView(nextActive.id)
} }
return true return true
}, },
[handleWindowControl, markForeignLocation, restoreToEntry] [
handleWindowControl,
markForeignLocation,
restoreToEntry,
scrollTabIntoView
]
) )
const closeTab = useCallback( const closeTab = useCallback(
@ -578,8 +617,9 @@ export const NavigationTabsProvider = ({ children }) => {
if (!entriesEqual(nextEntry, locationRef.current)) { if (!entriesEqual(nextEntry, locationRef.current)) {
restoreToEntry(nextEntry, { previousTabId }) restoreToEntry(nextEntry, { previousTabId })
} }
scrollTabIntoView(tab.id)
}, },
[markForeignLocation, restoreToEntry] [markForeignLocation, restoreToEntry, scrollTabIntoView]
) )
const handleTabDragStart = useCallback( const handleTabDragStart = useCallback(
@ -712,26 +752,23 @@ export const NavigationTabsProvider = ({ children }) => {
return next return next
}, []) }, [])
const setTabPage = useCallback( const setTabPage = useCallback(({ title, modelName, iconKey } = {}) => {
({ title, modelName, iconKey } = {}) => { const activeId = activeTabIdRef.current
const activeId = activeTabIdRef.current if (!activeId) return
if (!activeId) return
setTabs((current) => { setTabs((current) => {
if (current.length === 0) return current if (current.length === 0) return current
let changed = false let changed = false
const next = current.map((tab) => { const next = current.map((tab) => {
if (tab.id !== activeId) return tab if (tab.id !== activeId) return tab
const updated = applyPageMetaToTab(tab, { title, modelName, iconKey }) const updated = applyPageMetaToTab(tab, { title, modelName, iconKey })
if (updated !== tab) changed = true if (updated !== tab) changed = true
return updated return updated
})
return changed ? next : current
}) })
}, return changed ? next : current
[] })
) }, [])
const getTabListState = useCallback((tabId, scope) => { const getTabListState = useCallback((tabId, scope) => {
const tab = tabsRef.current.find((item) => item.id === tabId) const tab = tabsRef.current.find((item) => item.id === tabId)
@ -825,8 +862,7 @@ export const NavigationTabsProvider = ({ children }) => {
: restoredTabs[0].id : restoredTabs[0].id
setActiveTabId(nextActiveId) setActiveTabId(nextActiveId)
const activeTab = const activeTab =
restoredTabs.find((tab) => tab.id === nextActiveId) || restoredTabs.find((tab) => tab.id === nextActiveId) || restoredTabs[0]
restoredTabs[0]
const entry = getTabCurrentEntry(activeTab) const entry = getTabCurrentEntry(activeTab)
if (!entriesEqual(entry, location)) { if (!entriesEqual(entry, location)) {
restoreToEntry(entry) restoreToEntry(entry)