Enhance Dashboard Styles and Navigation with Improved Layout Adjustments
All checks were successful
farmcontrol/farmcontrol-ui/pipeline/head This commit looks good

- Added new CSS styles for dashboard menu container and tabs, improving visual alignment and spacing.
- Introduced logic to manage scroll edge fades for better user experience during tab navigation.
- Adjusted padding and margin properties for dashboard elements to enhance overall aesthetics and usability.
- Reformatted component code for improved readability and consistency.
This commit is contained in:
Tom Butcher 2026-09-18 15:03:19 +01:00
parent abe183c82a
commit 0b4b8128de
3 changed files with 136 additions and 53 deletions

View File

@ -185,6 +185,10 @@
bottom: 0; bottom: 0;
} }
.dashboard-menu-container-navigation-labels:after {
right: -6px;
}
.electron-navigation-leading, .electron-navigation-leading,
.electron-navigation-trailing { .electron-navigation-trailing {
align-self: stretch; align-self: stretch;
@ -3481,27 +3485,43 @@ body.objectKanbanColumnResizing * {
position: relative; position: relative;
} }
.dashboard-tabs-wrap .simplebar-content-wrapper {
overscroll-behavior-x: none;
}
.dashboard-tabs-wrap .simplebar-mask { .dashboard-tabs-wrap .simplebar-mask {
--tab-mask-left: 0px; --tab-mask-left: 4px;
--tab-mask-right: 0px; --tab-mask-right: 4px;
--tab-mask-fade-left: var(--tab-mask-fade);
--tab-mask-fade-right: var(--tab-mask-fade);
-webkit-clip-path: inset(0 var(--tab-mask-right) 0 var(--tab-mask-left)); -webkit-clip-path: inset(0 var(--tab-mask-right) 0 var(--tab-mask-left));
clip-path: inset(0 var(--tab-mask-right) 0 var(--tab-mask-left)); clip-path: inset(0 var(--tab-mask-right) 0 var(--tab-mask-left));
-webkit-mask-image: linear-gradient( -webkit-mask-image: linear-gradient(
to right, to right,
transparent var(--tab-mask-left), transparent var(--tab-mask-left),
#000 calc(var(--tab-mask-left) + var(--tab-mask-fade)), #000 calc(var(--tab-mask-left) + var(--tab-mask-fade-left)),
#000 calc(100% - var(--tab-mask-right) - var(--tab-mask-fade)), #000 calc(100% - var(--tab-mask-right) - var(--tab-mask-fade-right)),
transparent calc(100% - var(--tab-mask-right)) transparent calc(100% - var(--tab-mask-right))
); );
mask-image: linear-gradient( mask-image: linear-gradient(
to right, to right,
transparent var(--tab-mask-left), transparent var(--tab-mask-left),
#000 calc(var(--tab-mask-left) + var(--tab-mask-fade)), #000 calc(var(--tab-mask-left) + var(--tab-mask-fade-left)),
#000 calc(100% - var(--tab-mask-right) - var(--tab-mask-fade)), #000 calc(100% - var(--tab-mask-right) - var(--tab-mask-fade-right)),
transparent calc(100% - var(--tab-mask-right)) transparent calc(100% - var(--tab-mask-right))
); );
} }
.dashboard-tabs[data-at-start] .dashboard-tabs-wrap .simplebar-mask {
--tab-mask-left: 0px;
--tab-mask-fade-left: 0px;
}
.dashboard-tabs[data-at-end] .dashboard-tabs-wrap .simplebar-mask {
--tab-mask-right: 0px;
--tab-mask-fade-right: 0px;
}
.dashboard-tabs-wrap::before, .dashboard-tabs-wrap::before,
.dashboard-tabs-wrap::after { .dashboard-tabs-wrap::after {
content: ''; content: '';
@ -3572,7 +3592,7 @@ body.objectKanbanColumnResizing * {
} }
.dashboard-tab-item-container:first-child:before { .dashboard-tab-item-container:first-child:before {
left: -14px; left: -20px;
} }
.dashboard-tab-item-container:last-child:before { .dashboard-tab-item-container:last-child:before {
@ -3662,14 +3682,25 @@ body.objectKanbanColumnResizing * {
border-radius: 12px 12px 0 0; border-radius: 12px 12px 0 0;
} }
.dashboard-tab-item-active:before {
content: '';
position: absolute;
top: 0px;
left: 0px;
right: 0px;
bottom: 21px;
border-radius: 11px 11px 0 0;
z-index: -2;
background: var(--color-layout-background);
}
.dashboard-tab-item-active:after { .dashboard-tab-item-active:after {
content: ''; content: '';
position: absolute; position: absolute;
top: 1px; top: 14px;
left: 1px; left: 1px;
right: 1px; right: 1px;
bottom: -1px; bottom: -1px;
border-radius: 9px 9px 0 0;
z-index: -2; z-index: -2;
background: var(--color-layout-background); background: var(--color-layout-background);
} }
@ -3699,6 +3730,10 @@ body.objectKanbanColumnResizing * {
--tab-corner-center: calc( --tab-corner-center: calc(
var(--tab-corner-size) - var(--tab-corner-inset) - var(--tab-corner-radius) var(--tab-corner-size) - var(--tab-corner-inset) - var(--tab-corner-radius)
); );
--tab-fill-nudge-x: 3px;
--tab-fill-nudge-y: 4px;
--tab-fill-circle-x: calc(var(--tab-corner-center) + var(--tab-fill-nudge-x));
--tab-fill-circle-y: calc(var(--tab-corner-size) - var(--tab-corner-radius));
position: absolute; position: absolute;
bottom: -1px; bottom: -1px;
width: var(--tab-corner-size); width: var(--tab-corner-size);
@ -3718,35 +3753,39 @@ body.objectKanbanColumnResizing * {
.dashboard-tab-item-corner-fill { .dashboard-tab-item-corner-fill {
position: absolute; position: absolute;
inset: 0; width: 26px;
height: 26px;
bottom: calc(-1 * var(--tab-fill-nudge-y));
background: var(--color-layout-background); background: var(--color-layout-background);
-webkit-mask-repeat: no-repeat; -webkit-mask-repeat: no-repeat;
mask-repeat: no-repeat; mask-repeat: no-repeat;
} }
.dashboard-tab-item-corner-left .dashboard-tab-item-corner-fill { .dashboard-tab-item-corner-left .dashboard-tab-item-corner-fill {
left: calc(-1 * var(--tab-fill-nudge-x));
-webkit-mask-image: radial-gradient( -webkit-mask-image: radial-gradient(
circle at var(--tab-corner-center) var(--tab-corner-center), circle at var(--tab-fill-circle-x) var(--tab-fill-circle-y),
transparent calc(var(--tab-corner-radius) - 0.5px), transparent var(--tab-corner-radius),
#000 calc(var(--tab-corner-radius) - 0.5px) #000 var(--tab-corner-radius)
); );
mask-image: radial-gradient( mask-image: radial-gradient(
circle at var(--tab-corner-center) var(--tab-corner-center), circle at var(--tab-fill-circle-x) var(--tab-fill-circle-y),
transparent calc(var(--tab-corner-radius) - 0.5px), transparent var(--tab-corner-radius),
#000 calc(var(--tab-corner-radius) - 0.5px) #000 var(--tab-corner-radius)
); );
} }
.dashboard-tab-item-corner-right .dashboard-tab-item-corner-fill { .dashboard-tab-item-corner-right .dashboard-tab-item-corner-fill {
right: calc(-1 * var(--tab-fill-nudge-x));
-webkit-mask-image: radial-gradient( -webkit-mask-image: radial-gradient(
circle at calc(100% - var(--tab-corner-center)) var(--tab-corner-center), circle at calc(100% - var(--tab-fill-circle-x)) var(--tab-fill-circle-y),
transparent calc(var(--tab-corner-radius) - 0.5px), transparent var(--tab-corner-radius),
#000 calc(var(--tab-corner-radius) - 0.5px) #000 var(--tab-corner-radius)
); );
mask-image: radial-gradient( mask-image: radial-gradient(
circle at calc(100% - var(--tab-corner-center)) var(--tab-corner-center), circle at calc(100% - var(--tab-fill-circle-x)) var(--tab-fill-circle-y),
transparent calc(var(--tab-corner-radius) - 0.5px), transparent var(--tab-corner-radius),
#000 calc(var(--tab-corner-radius) - 0.5px) #000 var(--tab-corner-radius)
); );
} }
@ -3797,7 +3836,9 @@ body.objectKanbanColumnResizing * {
.dashboard-tabs[data-sticky-edge] .dashboard-tab-item-container:before, .dashboard-tabs[data-sticky-edge] .dashboard-tab-item-container:before,
.dashboard-tabs[data-sticky-edge] .dashboard-tabs-add-button-container:before, .dashboard-tabs[data-sticky-edge] .dashboard-tabs-add-button-container:before,
.dashboard-tabs[data-sticky-edge] .dashboard-tabs-wrap::before, .dashboard-tabs[data-sticky-edge] .dashboard-tabs-wrap::before,
.dashboard-tabs[data-sticky-edge] .dashboard-tabs-wrap::after { .dashboard-tabs[data-sticky-edge] .dashboard-tabs-wrap::after,
.dashboard-tabs[data-at-start] .dashboard-tabs-wrap::before,
.dashboard-tabs[data-at-end] .dashboard-tabs-wrap::after {
content: none; content: none;
display: none; display: none;
} }
@ -3897,9 +3938,10 @@ body.objectKanbanColumnResizing * {
} }
.dashboard-tabs-add-button { .dashboard-tabs-add-button {
padding-inline: 4px; padding-inline: 5px;
min-width: 20px; min-width: 22px;
margin-top: 2px; margin-top: 5px;
margin-right: 5px;
} }
.dashboard-tabs-add-button-container { .dashboard-tabs-add-button-container {
@ -3911,7 +3953,7 @@ body.objectKanbanColumnResizing * {
.dashboard-tabs-add-button-container:before { .dashboard-tabs-add-button-container:before {
content: ''; content: '';
position: absolute; position: absolute;
left: 12px; left: 15px;
right: 0; right: 0;
bottom: 0; bottom: 0;
border-bottom: 1px solid var(--color-header-border); border-bottom: 1px solid var(--color-header-border);

View File

@ -1,5 +1,12 @@
// DashboardNavigation.js // DashboardNavigation.js
import { useContext, useEffect, useState, useMemo, useCallback, memo } from 'react' import {
useContext,
useEffect,
useState,
useMemo,
useCallback,
memo
} from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import { import {
Menu, Menu,
@ -12,6 +19,7 @@ import {
Typography, Typography,
Popover Popover
} from 'antd' } from 'antd'
import classNames from 'classnames'
import { AuthContext } from '../context/AuthContext' import { AuthContext } from '../context/AuthContext'
import { SpotlightContext } from '../context/SpotlightContext' import { SpotlightContext } from '../context/SpotlightContext'
import { ApiServerContext } from '../context/ApiServerContext' import { ApiServerContext } from '../context/ApiServerContext'
@ -89,7 +97,11 @@ const NAV_OVERFLOW_BUTTON_STYLE = { marginBottom: '4px' }
const navigationFadeClass = (visible) => const navigationFadeClass = (visible) =>
`dashboard-navigation-fade${visible ? '' : ' dashboard-navigation-fade-hidden'}` `dashboard-navigation-fade${visible ? '' : ' dashboard-navigation-fade-hidden'}`
const NavigationFade = memo(function NavigationFade({ visible, children, style }) { const NavigationFade = memo(function NavigationFade({
visible,
children,
style
}) {
return ( return (
<div <div
className={navigationFadeClass(visible)} className={navigationFadeClass(visible)}
@ -213,27 +225,33 @@ DashboardNavigationTrailingItems.propTypes = {
onCheckUpdates: PropTypes.func onCheckUpdates: PropTypes.func
} }
const DashboardElectronCenterItems = memo(function DashboardElectronCenterItems({ const DashboardElectronCenterItems = memo(
menu, function DashboardElectronCenterItems({ menu, showNavigationLabels }) {
showNavigationLabels return (
}) { <>
return ( <div
<> className={classNames(
<div className='dashboard-menu-container'>{menu}</div> 'dashboard-menu-container',
<Divider showNavigationLabels && 'dashboard-menu-container-navigation-labels'
type='vertical' )}
style={ >
showNavigationLabels {menu}
? NAV_ELECTRON_DIVIDER_STYLE_WITH_LABELS </div>
: NAV_ELECTRON_DIVIDER_STYLE <Divider
} type='vertical'
/> style={
<div style={NAV_ELECTRON_TABS_STYLE}> showNavigationLabels
<DashboardTabs /> ? NAV_ELECTRON_DIVIDER_STYLE_WITH_LABELS
</div> : NAV_ELECTRON_DIVIDER_STYLE
</> }
) />
}) <div style={NAV_ELECTRON_TABS_STYLE}>
<DashboardTabs />
</div>
</>
)
}
)
DashboardElectronCenterItems.propTypes = { DashboardElectronCenterItems.propTypes = {
menu: PropTypes.node, menu: PropTypes.node,
@ -520,7 +538,11 @@ const DashboardNavigation = () => {
}, [checkForUpdates]) }, [checkForUpdates])
const overflowedIndicator = useMemo( const overflowedIndicator = useMemo(
() => ( () => (
<Button type='text' icon={<MenuIcon />} style={NAV_OVERFLOW_BUTTON_STYLE} /> <Button
type='text'
icon={<MenuIcon />}
style={NAV_OVERFLOW_BUTTON_STYLE}
/>
), ),
[] []
) )

View File

@ -191,6 +191,22 @@ const setSimplebarMaskInset = (maskEl, edge, inset) => {
} }
} }
const setScrollEdgeFades = (root, scrollEl) => {
if (!root) return
if (!scrollEl) {
root.dataset.atStart = ''
root.dataset.atEnd = ''
return
}
const atStart = scrollEl.scrollLeft <= 1
const atEnd =
scrollEl.scrollLeft + scrollEl.clientWidth >= scrollEl.scrollWidth - 1
if (atStart) root.dataset.atStart = ''
else delete root.dataset.atStart
if (atEnd) root.dataset.atEnd = ''
else delete root.dataset.atEnd
}
const setStickyStripLine = (root, lineEl, edge, lineInset) => { const setStickyStripLine = (root, lineEl, edge, lineInset) => {
if (root) { if (root) {
if (!edge) delete root.dataset.stickyEdge if (!edge) delete root.dataset.stickyEdge
@ -244,6 +260,7 @@ const bindActiveTabStickyScroll = (scrollEl, list, cloneEl, root, lineEl) => {
state.updating = true state.updating = true
try { try {
setScrollEdgeFades(root, scrollEl)
const container = list.querySelector( const container = list.querySelector(
'.dashboard-tab-item-container-active' '.dashboard-tab-item-container-active'
) )
@ -355,6 +372,7 @@ const bindActiveTabStickyScroll = (scrollEl, list, cloneEl, root, lineEl) => {
const observer = new ResizeObserver(scheduleUpdate) const observer = new ResizeObserver(scheduleUpdate)
observer.observe(scrollEl) observer.observe(scrollEl)
if (wrapperEl !== scrollEl) observer.observe(wrapperEl) if (wrapperEl !== scrollEl) observer.observe(wrapperEl)
observer.observe(list)
return () => { return () => {
scrollEl.removeEventListener('scroll', scheduleUpdate) scrollEl.removeEventListener('scroll', scheduleUpdate)
@ -363,6 +381,8 @@ const bindActiveTabStickyScroll = (scrollEl, list, cloneEl, root, lineEl) => {
if (frame) window.cancelAnimationFrame(frame) if (frame) window.cancelAnimationFrame(frame)
setOriginalStickyState(state.activeEl, false) setOriginalStickyState(state.activeEl, false)
clearStickyVisuals() clearStickyVisuals()
delete root.dataset.atStart
delete root.dataset.atEnd
} }
} }
@ -613,7 +633,7 @@ const DashboardTabs = () => {
onDrop={handleStripDrop} onDrop={handleStripDrop}
> >
<ScrollBox className='dashboard-tabs-wrap' horizontal> <ScrollBox className='dashboard-tabs-wrap' horizontal>
<Flex align='flex-start' gap={8} className='dashboard-tabs-inner'> <Flex align='flex-start' gap={5} className='dashboard-tabs-inner'>
<div <div
ref={listRef} ref={listRef}
className={classNames('dashboard-tabs-list', { className={classNames('dashboard-tabs-list', {
@ -660,7 +680,6 @@ const DashboardTabs = () => {
<div className='dashboard-tabs-add-button-container'> <div className='dashboard-tabs-add-button-container'>
<Button <Button
type='text' type='text'
size='small'
className='dashboard-tabs-add-button electrobun-webkit-app-region-no-drag' className='dashboard-tabs-add-button electrobun-webkit-app-region-no-drag'
onClick={addTab} onClick={addTab}
icon={<PlusIcon style={{ fontSize: 14, marginTop: 3 }} />} icon={<PlusIcon style={{ fontSize: 14, marginTop: 3 }} />}