Enhance Dashboard Navigation Fade Logic and Styles
All checks were successful
farmcontrol/farmcontrol-ui/pipeline/head This commit looks good

- Introduced a new hook for managing delayed visibility of navigation elements, improving fade transitions.
- Updated CSS transitions for smoother visibility changes, ensuring better user experience during interactions.
- Refactored NavigationFade component to accept a className prop for enhanced styling flexibility.
- Adjusted logic for rendering window titles and dashboard items based on Electron context, improving overall navigation behavior.
This commit is contained in:
Tom Butcher 2026-09-18 16:19:33 +01:00
parent 89fc70a8ea
commit f56646f906
2 changed files with 54 additions and 19 deletions

View File

@ -124,6 +124,7 @@
.dashboard-navigation-fade { .dashboard-navigation-fade {
opacity: 1; opacity: 1;
visibility: visible; visibility: visible;
/* Incoming chrome waits for the outgoing fade, then fades in. */
transition: transition:
opacity 0.2s ease 0.2s, opacity 0.2s ease 0.2s,
visibility 0s linear 0.2s; visibility 0s linear 0.2s;
@ -133,9 +134,14 @@
opacity: 0; opacity: 0;
visibility: hidden; visibility: hidden;
pointer-events: none; pointer-events: none;
/*
Visibility must use a non-zero duration on hide. A 0s visibility
transition makes WebKit apply hidden immediately, so opacity never
animates and the chrome glitches out instead of fading.
*/
transition: transition:
opacity 0.2s ease 0s, opacity 0.2s ease 0s,
visibility 0s linear 0.2s; visibility 0.2s linear 0s;
} }
.dashboard-navigation-window-title { .dashboard-navigation-window-title {
@ -162,7 +168,7 @@
pointer-events: none; pointer-events: none;
opacity: 1; opacity: 1;
/* Wait for outgoing chrome to finish fading before this line appears. */ /* Wait for outgoing chrome to finish fading before this line appears. */
transition: opacity 0s ease 0s; transition: opacity 0.2s ease 0s;
} }
.dashboard-navigation-center-border-hidden { .dashboard-navigation-center-border-hidden {

View File

@ -86,19 +86,40 @@ const NAV_BADGE_STYLE = { padding: 0, fontWeight: 600 }
const NAV_UPDATE_TAG_STYLE = { cursor: 'pointer', margin: '2px 0 0 0' } const NAV_UPDATE_TAG_STYLE = { cursor: 'pointer', margin: '2px 0 0 0' }
const NAV_OVERFLOW_BUTTON_STYLE = { marginBottom: '4px' } const NAV_OVERFLOW_BUTTON_STYLE = { marginBottom: '4px' }
const NAV_FADE_MS = 200
const navigationFadeClass = (visible) => const navigationFadeClass = (visible) =>
`dashboard-navigation-fade${visible ? '' : ' dashboard-navigation-fade-hidden'}` `dashboard-navigation-fade${visible ? '' : ' dashboard-navigation-fade-hidden'}`
const useDelayedInactive = (visible) => {
const [inactive, setInactive] = useState(!visible)
useEffect(() => {
if (visible) {
setInactive(false)
return undefined
}
const timeoutId = window.setTimeout(() => setInactive(true), NAV_FADE_MS)
return () => window.clearTimeout(timeoutId)
}, [visible])
return inactive
}
const NavigationFade = memo(function NavigationFade({ const NavigationFade = memo(function NavigationFade({
visible, visible,
children, children,
style style,
className
}) { }) {
const inactive = useDelayedInactive(visible)
return ( return (
<div <div
className={navigationFadeClass(visible)} className={classNames(navigationFadeClass(visible), className)}
inert={visible ? undefined : true} inert={inactive ? true : undefined}
aria-hidden={visible ? undefined : true} aria-hidden={inactive ? true : undefined}
style={style} style={style}
> >
{children} {children}
@ -109,7 +130,8 @@ const NavigationFade = memo(function NavigationFade({
NavigationFade.propTypes = { NavigationFade.propTypes = {
visible: PropTypes.bool, visible: PropTypes.bool,
children: PropTypes.node, children: PropTypes.node,
style: PropTypes.object style: PropTypes.object,
className: PropTypes.string
} }
const DashboardNavigationTrailingItems = memo( const DashboardNavigationTrailingItems = memo(
@ -246,13 +268,17 @@ DashboardElectronCenterItems.propTypes = {
const DashboardNavigationTrailing = memo(function DashboardNavigationTrailing({ const DashboardNavigationTrailing = memo(function DashboardNavigationTrailing({
showControls, showControls,
isOtherApp, isOtherApp,
isElectron,
...itemProps ...itemProps
}) { }) {
return ( return (
<> <>
<div className='electrobun-webkit-app-region-no-drag'> <div className='electrobun-webkit-app-region-no-drag'>
<NavigationFade visible={showControls}> <NavigationFade visible={!isElectron || showControls}>
<DashboardNavigationTrailingItems {...itemProps} /> <DashboardNavigationTrailingItems
isElectron={isElectron}
{...itemProps}
/>
</NavigationFade> </NavigationFade>
</div> </div>
{isOtherApp ? <DashboardWindowButtons /> : null} {isOtherApp ? <DashboardWindowButtons /> : null}
@ -470,6 +496,11 @@ const DashboardNavigation = () => {
const showMobileLogo = !isElectron && isMobile const showMobileLogo = !isElectron && isMobile
useEffect(() => { useEffect(() => {
if (!isElectron) {
setAnyModalVisible(false)
return
}
const isModalWrapVisible = (node) => { const isModalWrapVisible = (node) => {
if (!(node instanceof Element)) return false if (!(node instanceof Element)) return false
if (node.getAttribute('aria-hidden') === 'true') return false if (node.getAttribute('aria-hidden') === 'true') return false
@ -508,7 +539,7 @@ const DashboardNavigation = () => {
observer.disconnect() observer.disconnect()
if (frameId) window.cancelAnimationFrame(frameId) if (frameId) window.cancelAnimationFrame(frameId)
} }
}, []) }, [isElectron])
const controlsReady = !isElectron || authenticated const controlsReady = !isElectron || authenticated
const showControls = controlsReady && !anyModalVisible const showControls = controlsReady && !anyModalVisible
@ -618,16 +649,15 @@ const DashboardNavigation = () => {
</> </>
) )
const showWindowTitle = isElectron && !showControls
const windowTitle = ( const windowTitle = (
<div <NavigationFade
className={`dashboard-navigation-window-title ${navigationFadeClass( visible={showWindowTitle}
!showControls className='dashboard-navigation-window-title'
)}`}
aria-hidden={showControls}
inert={showControls ? true : undefined}
> >
<Text type='secondary'>Farm Control</Text> <Text type='secondary'>Farm Control</Text>
</div> </NavigationFade>
) )
const navigationCenterBorder = ( const navigationCenterBorder = (
@ -644,12 +674,11 @@ const DashboardNavigation = () => {
{navigationLeading} {navigationLeading}
<div className='dashboard-navigation-center'> <div className='dashboard-navigation-center'>
{!isMobile ? ( {!isMobile ? (
<NavigationFade visible={showControls} style={NAV_CENTER_FADE_STYLE}> <NavigationFade visible style={NAV_CENTER_FADE_STYLE}>
{menu} {menu}
</NavigationFade> </NavigationFade>
) : null} ) : null}
{windowTitle} {windowTitle}
{navigationCenterBorder}
</div> </div>
{navigationTrailing} {navigationTrailing}
</Flex> </Flex>