fix: focused elements could be hidden by the header on top
Massimo Melina committed
Sep 16, 2025 at 15:44 UTC
1fa8f8fd04ee2106cf5fc04f5a00b0655fa50b59
3 files changed
+17
-7
admin/src/App.ts
+2
-2
@@ -14,7 +14,7 @@ import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'
14
import ConfigFilePage from './ConfigFilePage'
15
import { useSnapState } from './state'
16
import { useEventListener } from 'usehooks-ts'
17
-import { AriaOnly, isMac, xlate } from './misc'
17
+import { AriaOnly, isMac, useFixSticky, xlate } from './misc'
18
import { getLocale } from './locale'
19
import { fillFlexParentSx } from './DataTable'
20
@@ -130,7 +130,7 @@ function itemTitle(idx: number) {
130
}
131
132
function StickyBar({ title, titleSide, openMenu, props }: { props?: BoxProps, titleSide?: ReactNode, title?: string, openMenu: ()=>void }) {
133
- return h(AppBar, { position: 'sticky', sx: { mb: 1 } },
133
+ return h(AppBar, { ref: useFixSticky(), position: 'sticky', sx: { mb: 1 }, },
134
h(Toolbar, {},
135
h(IconButton, {
136
size: 'large',
frontend/src/Head.ts
+2
-2
@@ -1,7 +1,7 @@
1
// This file is part of HFS - Copyright 2021-2023, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt
2
3
import { createElement as h, Fragment, useMemo } from 'react'
4
-import { formatBytes, hIcon } from './misc'
4
+import { formatBytes, hIcon, useFixSticky } from './misc'
5
import { CustomCode, Spinner } from './components'
6
import { useSnapState } from './state'
7
import { MenuPanel } from './menu'
@@ -11,7 +11,7 @@ import i18n from './i18n'
11
const { useI18N } = i18n
12
13
export function Head() {
14
- return h('header', {},
14
+ return h('header', { ref: useFixSticky() },
15
h(MenuPanel),
16
h(CustomCode, { name: 'afterMenuBar' }),
17
h(Breadcrumbs),
shared/react.ts
+13
-3
@@ -126,14 +126,24 @@ export function useIsMobile() {
126
return useMediaQuery('(pointer:coarse)')
127
}
128
129
+// workaround for the usability problem caused by sticky headers/footers. Just assign the returned value as ref prop of your sticky element.
130
+export function useFixSticky() {
131
+ return useOnResize((_w, h, _el, style) => {
132
+ Object.assign(document.documentElement.style, {
133
+ scrollPaddingTop: `${h + (parseFloat(style.top) || 0)}px`,
134
+ scrollPaddingBottom: `${h + (parseFloat(style.bottom) || 0)}px`,
135
+ })
136
+ }).refToPass
137
+}
138
+
139
// returns props to assign to your component, and a copy of the ref; calls back with [width, height]
130
-export function useOnResize(cb: Callback<[number, number]>) {
140
+export function useOnResize(cb: (width: number, height: number, target: HTMLElement, style: CSSStyleDeclaration) => any) {
141
const observer = useMemo(() =>
142
new ResizeObserver(_.debounce(([{ contentRect: r, target }]) => {
143
const style = getComputedStyle(target)
144
const pw = parseFloat(style.paddingLeft) + parseFloat(style.paddingRight)
145
const ph = parseFloat(style.paddingTop) + parseFloat(style.paddingBottom)
136
- cb([r.width + pw, r.height + ph])
146
+ cb(r.width + pw, r.height + ph, target, style)
147
}, 10)),
148
[])
149
const ref = useRef<HTMLElement>()
@@ -150,7 +160,7 @@ export function useOnResize(cb: Callback<[number, number]>) {
160
161
export function useGetSize() {
162
const [size, setSize] = useState<[number,number]>()
153
- const { refToPass, ref } = useOnResize(setSize)
163
+ const { refToPass, ref } = useOnResize((w, h) => setSize([w, h]))
164
return useMemo(() => ({
165
w: size?.[0],
166
h: size?.[1],