feat: implement prevent back scroll functionality in various dashboard components

This commit is contained in:
kastov 2026-02-19 03:05:26 +03:00
parent 6f7a0d7afc
commit d439a0c7dc
No known key found for this signature in database
GPG key ID: 1B27BE29057F4C90
5 changed files with 80 additions and 4 deletions

View file

@ -1,8 +1,8 @@
import { Split } from '@gfazioli/mantine-split-pane'
import { useEffect, useLayoutEffect } from 'react'
import { useTranslation } from 'react-i18next'
import { Stack } from '@mantine/core'
import { motion } from 'motion/react'
import { useEffect } from 'react'
import { CreateInfraBillingRecordDrawerWidget } from '@widgets/dashboard/infra-billing/create-infra-billing-record-modal/create-infra-billing-record.modal.widget'
import { CreateInfraBillingNodeModalWidget } from '@widgets/dashboard/infra-billing/create-infra-billing-node-modal/create-infra-billing-node.modal.widget'
@ -14,6 +14,7 @@ import { InfraProvidersTableWidget } from '@widgets/dashboard/infra-billing/infr
import { UpdateBillingDateModalWidget } from '@widgets/dashboard/infra-billing/update-billing-date-modal'
import { StatsWidget } from '@widgets/dashboard/infra-billing/stats-widget/stats.widget'
import { useAppshellStoreActions } from '@entities/dashboard/appshell'
import { preventBackScrollTables } from '@shared/utils/misc'
import { Page } from '@shared/ui/page'
export const InfraBillingPageComponent = () => {
@ -21,6 +22,15 @@ export const InfraBillingPageComponent = () => {
const { hideDesktopSidebar } = useAppshellStoreActions()
useLayoutEffect(() => {
document.body.addEventListener('wheel', preventBackScrollTables, {
passive: false
})
return () => {
document.body.removeEventListener('wheel', preventBackScrollTables)
}
}, [])
useEffect(() => {
hideDesktopSidebar()
}, [])

View file

@ -1,3 +1,17 @@
function findScrollableParent(el: Element | null, horizontal: boolean): Element | null {
while (el && el !== document.body) {
const style = window.getComputedStyle(el)
const overflow = horizontal ? style.overflowX : style.overflowY
if (overflow === 'auto' || overflow === 'scroll') {
return el
}
// eslint-disable-next-line no-param-reassign
el = el.parentElement
}
return null
}
export function preventBackScroll(event: WheelEvent): void {
if (event.deltaX === 0) {
return
@ -5,3 +19,25 @@ export function preventBackScroll(event: WheelEvent): void {
event.preventDefault()
event.stopPropagation()
}
export function preventBackScrollTables(event: WheelEvent): void {
if (event.deltaX === 0) {
return
}
const scrollable = findScrollableParent(event.target as Element, true)
if (scrollable) {
const atLeftEdge = scrollable.scrollLeft <= 0 && event.deltaX < 0
const atRightEdge =
scrollable.scrollLeft + scrollable.clientWidth >= scrollable.scrollWidth - 1 &&
event.deltaX > 0
if (!atLeftEdge && !atRightEdge) {
return
}
}
event.preventDefault()
event.stopPropagation()
}

View file

@ -11,14 +11,15 @@ import {
useMantineReactTable
} from 'mantine-react-table'
import { TbDeviceAnalytics, TbRefresh, TbRestore } from 'react-icons/tb'
import { useCallback, useLayoutEffect, useMemo, useState } from 'react'
import { GetAllHwidDevicesCommand } from '@remnawave/backend-contract'
import { ActionIcon, ActionIconGroup, Tooltip } from '@mantine/core'
import { useCallback, useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { PiUserCircle } from 'react-icons/pi'
import { useHwidInspectorTableColumns } from '@features/dashboard/hwid-inspector/hwid-inspector-table/model/use-hwid-inspector-table-columns'
import { useUserModalStoreActions } from '@entities/dashboard/user-modal-store'
import { preventBackScrollTables } from '@shared/utils/misc'
import { useGetAllHwidDevices } from '@shared/api/hooks'
import { DataTableShared } from '@shared/ui/table'
import { sToMs } from '@shared/utils/time-utils'
@ -67,6 +68,15 @@ export function HwidInspectorTableWidget() {
const filteredData = useMemo(() => usersResponse, [usersResponse])
useLayoutEffect(() => {
document.body.addEventListener('wheel', preventBackScrollTables, {
passive: false
})
return () => {
document.body.removeEventListener('wheel', preventBackScrollTables)
}
}, [])
const table = useMantineReactTable({
columns: tableColumns,
data: filteredData?.devices ?? [],

View file

@ -12,14 +12,15 @@ import {
} from 'mantine-react-table'
import { TbExternalLink, TbRefresh, TbReportAnalytics, TbRestore } from 'react-icons/tb'
import { GetSubscriptionRequestHistoryCommand } from '@remnawave/backend-contract'
import { useCallback, useLayoutEffect, useMemo, useState } from 'react'
import { ActionIcon, ActionIconGroup, Tooltip } from '@mantine/core'
import { useCallback, useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { PiUserCircle } from 'react-icons/pi'
import { useSrhInspectorTableColumns } from '@features/dashboard/srh-inspector/srh-inspector-table/model/use-srh-inspector-table-columns'
import { useUserModalStoreActions } from '@entities/dashboard/user-modal-store'
import { useGetSubscriptionRequestHistory } from '@shared/api/hooks'
import { preventBackScrollTables } from '@shared/utils/misc'
import { DataTableShared } from '@shared/ui/table'
import { sToMs } from '@shared/utils/time-utils'
@ -44,6 +45,15 @@ export function SrhInspectorTableWidget() {
Object.fromEntries(tableColumns.map(({ accessorKey }) => [accessorKey, 'contains']))
)
useLayoutEffect(() => {
document.body.addEventListener('wheel', preventBackScrollTables, {
passive: false
})
return () => {
document.body.removeEventListener('wheel', preventBackScrollTables)
}
}, [])
const params = {
start: pagination.pageIndex * pagination.pageSize,
size: pagination.pageSize,

View file

@ -5,10 +5,10 @@ import {
MRT_SortingState,
useMantineReactTable
} from 'mantine-react-table'
import { useLayoutEffect, useMemo, useState } from 'react'
import { notifications } from '@mantine/notifications'
import { PiUsersDuotone } from 'react-icons/pi'
import { useTranslation } from 'react-i18next'
import { useMemo, useState } from 'react'
import {
useUsersTableStoreActions,
@ -33,6 +33,7 @@ import {
import { useUserTableColumns } from '@features/dashboard/users/users-table/model/use-table-columns'
import { UserActionGroupFeature } from '@features/dashboard/users/users-action-group'
import { useUserModalStoreActions } from '@entities/dashboard/user-modal-store'
import { preventBackScrollTables } from '@shared/utils/misc'
import { DataTableShared } from '@shared/ui/table'
import { sToMs } from '@shared/utils/time-utils'
@ -65,6 +66,15 @@ export function UserTableWidget() {
Object.fromEntries(tableColumns.map(({ accessorKey }) => [accessorKey, 'contains']))
)
useLayoutEffect(() => {
document.body.addEventListener('wheel', preventBackScrollTables, {
passive: false
})
return () => {
document.body.removeEventListener('wheel', preventBackScrollTables)
}
}, [])
const params = {
start: pagination.pageIndex * pagination.pageSize,
size: pagination.pageSize,