From 745f4c6ba252113b45a1008820076ebd54899ea2 Mon Sep 17 00:00:00 2001 From: Alexander Zinchuk Date: Thu, 21 Aug 2025 12:05:22 +0200 Subject: [PATCH] =?UTF-8?q?[Refactoring]=20`seMarkScrolled`=20=E2=86=92=20?= =?UTF-8?q?`useScrollNotch`:=20Use=20pseudo-element=20instead=20of=20borde?= =?UTF-8?q?r?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/left/settings/Settings.scss | 10 ++-- src/components/left/settings/Settings.tsx | 4 +- src/components/right/Profile.scss | 23 -------- src/components/right/RightColumn.tsx | 4 +- src/hooks/useMarkScrolled/useMarkScrolled.ts | 38 ------------- src/hooks/useScrollNotch.ts | 58 ++++++++++++++++++++ src/styles/index.scss | 25 +++++++++ 7 files changed, 91 insertions(+), 71 deletions(-) delete mode 100644 src/hooks/useMarkScrolled/useMarkScrolled.ts create mode 100644 src/hooks/useScrollNotch.ts diff --git a/src/components/left/settings/Settings.scss b/src/components/left/settings/Settings.scss index 5936e65f2..e48709753 100644 --- a/src/components/left/settings/Settings.scss +++ b/src/components/left/settings/Settings.scss @@ -3,6 +3,10 @@ #Settings { height: 100%; + .with-notch::before { + top: var(--header-height); + } + > .Transition_slide { overflow: hidden; display: flex; @@ -40,12 +44,6 @@ .settings-content { overflow-y: scroll; height: calc(100% - var(--header-height)); - border-top: 1px solid transparent; - transition: border-top-color 0.2s ease-in-out; - - &.scrolled { - border-top-color: var(--color-borders); - } &.password-form .input-group.error label::first-letter { text-transform: uppercase; diff --git a/src/components/left/settings/Settings.tsx b/src/components/left/settings/Settings.tsx index 31b29faf2..ca79ab74a 100644 --- a/src/components/left/settings/Settings.tsx +++ b/src/components/left/settings/Settings.tsx @@ -11,7 +11,7 @@ import { resolveTransitionName } from '../../../util/resolveTransitionName.ts'; import useTwoFaReducer from '../../../hooks/reducers/useTwoFaReducer'; import useLastCallback from '../../../hooks/useLastCallback'; -import useMarkScrolled from '../../../hooks/useMarkScrolled/useMarkScrolled'; +import useScrollNotch from '../../../hooks/useScrollNotch.ts'; import Transition from '../../ui/Transition'; import SettingsFolders from './folders/SettingsFolders'; @@ -172,7 +172,7 @@ const Settings: FC = ({ const [twoFaState, twoFaDispatch] = useTwoFaReducer(); const [privacyPasscode, setPrivacyPasscode] = useState(''); - useMarkScrolled({ + useScrollNotch({ containerRef, selector: '.settings-content', }, [currentScreen]); diff --git a/src/components/right/Profile.scss b/src/components/right/Profile.scss index b0e547821..52887d085 100644 --- a/src/components/right/Profile.scss +++ b/src/components/right/Profile.scss @@ -8,29 +8,6 @@ height: 100%; - &::before { - content: ""; - - position: absolute; - z-index: 1; - top: 0; - left: 0; - - width: 100%; - height: 1px; - - opacity: 0; - background-color: var(--color-borders); - - transition: opacity 0.2s ease-in-out; - } - - &.scrolled { - &::before { - opacity: 1; - } - } - > .profile-info > .ChatInfo { grid-area: chat_info; diff --git a/src/components/right/RightColumn.tsx b/src/components/right/RightColumn.tsx index e55ddc140..1c74e1294 100644 --- a/src/components/right/RightColumn.tsx +++ b/src/components/right/RightColumn.tsx @@ -22,7 +22,7 @@ import useCurrentOrPrev from '../../hooks/useCurrentOrPrev'; import useHistoryBack from '../../hooks/useHistoryBack'; import useLastCallback from '../../hooks/useLastCallback'; import useLayoutEffectWithPrevDeps from '../../hooks/useLayoutEffectWithPrevDeps'; -import useMarkScrolled from '../../hooks/useMarkScrolled/useMarkScrolled'; +import useScrollNotch from '../../hooks/useScrollNotch.ts'; import useWindowSize from '../../hooks/window/useWindowSize'; import Transition from '../ui/Transition'; @@ -140,7 +140,7 @@ const RightColumn: FC = ({ const renderingContentKey = useCurrentOrPrev(contentKey, true, !isChatSelected) ?? -1; - useMarkScrolled({ + useScrollNotch({ containerRef, selector: ':scope .custom-scroll, :scope .panel-content', }, [contentKey, managementScreen, chatId, threadId]); diff --git a/src/hooks/useMarkScrolled/useMarkScrolled.ts b/src/hooks/useMarkScrolled/useMarkScrolled.ts deleted file mode 100644 index 87c627637..000000000 --- a/src/hooks/useMarkScrolled/useMarkScrolled.ts +++ /dev/null @@ -1,38 +0,0 @@ -import type { ElementRef } from '../../lib/teact/teact'; -import { useEffect } from '../../lib/teact/teact'; - -import { requestMutation } from '../../lib/fasterdom/fasterdom'; -import { throttle } from '../../util/schedulers'; - -const THROTTLE_DELAY = 100; - -const useMarkScrolled = ({ - containerRef, selector, -}: { - containerRef: ElementRef; - selector: string; -}, deps: unknown[]) => { - useEffect(() => { - const elements = containerRef?.current?.querySelectorAll(selector); - if (!elements?.length) return undefined; - - const handleScroll = throttle((event: Event) => { - const target = event.target as HTMLElement; - const isScrolled = target.scrollTop > 0; - requestMutation(() => { - target.classList.toggle('scrolled', isScrolled); - }); - }, THROTTLE_DELAY); - - elements.forEach((el) => el.addEventListener('scroll', handleScroll, { passive: true })); - // Trigger the scroll handler immediately to apply the current state - elements.forEach((el) => el.dispatchEvent(new Event('scroll', { bubbles: false }))); - - return () => { - elements.forEach((el) => el.removeEventListener('scroll', handleScroll)); - }; - // eslint-disable-next-line react-hooks-static-deps/exhaustive-deps - }, [containerRef, selector, ...deps]); -}; - -export default useMarkScrolled; diff --git a/src/hooks/useScrollNotch.ts b/src/hooks/useScrollNotch.ts new file mode 100644 index 000000000..4e8ca8451 --- /dev/null +++ b/src/hooks/useScrollNotch.ts @@ -0,0 +1,58 @@ +import type { ElementRef } from '@teact'; +import { useEffect, useLayoutEffect } from '@teact'; +import { addExtraClass, removeExtraClass, toggleExtraClass } from '@teact/teact-dom.ts'; + +import { requestMutation } from '../lib/fasterdom/fasterdom.ts'; +import { throttle } from '../util/schedulers.ts'; + +const THROTTLE_DELAY = 100; + +const useScrollNotch = ({ + containerRef, + selector, +}: { + containerRef: ElementRef; + selector: string; +}, deps: unknown[]) => { + useLayoutEffect(() => { + const elements = containerRef.current?.querySelectorAll(selector); + if (!elements?.length) return undefined; + + const handleScroll = throttle((event: Event) => { + const target = event.target as HTMLElement; + const isScrolled = target.scrollTop > 0; + + requestMutation(() => { + toggleExtraClass(target, 'scrolled', isScrolled); + }); + }, THROTTLE_DELAY); + + elements.forEach((el) => { + addExtraClass(el, 'with-notch'); + el.addEventListener('scroll', handleScroll, { passive: true }); + }); + + return () => { + elements.forEach((el) => { + el.removeEventListener('scroll', handleScroll); + removeExtraClass(el, 'with-notch'); + }); + }; + // eslint-disable-next-line react-hooks-static-deps/exhaustive-deps + }, [containerRef, selector, ...deps]); + + useEffect(() => { + const elements = containerRef.current?.querySelectorAll(selector); + if (!elements?.length) return undefined; + + elements.forEach((el) => { + const isScrolled = el.scrollTop > 0; + requestMutation(() => { + toggleExtraClass(el, 'scrolled', isScrolled); + }); + }); + // eslint-disable-next-line react-hooks-static-deps/exhaustive-deps + }, [containerRef, selector, ...deps]); +}; + +export default useScrollNotch; diff --git a/src/styles/index.scss b/src/styles/index.scss index d43c4b63d..4841b3ca2 100644 --- a/src/styles/index.scss +++ b/src/styles/index.scss @@ -359,6 +359,31 @@ body:not(.is-ios) { height: 100%; } +.with-notch { + &::before { + content: ""; + + position: absolute; + z-index: 1; + top: 0; + left: 0; + + width: 100%; + height: 1px; + + opacity: 0; + background-color: var(--color-borders); + + transition: opacity 0.2s ease-in-out; + } + + &.scrolled { + &::before { + opacity: 1; + } + } +} + @keyframes grow-icon { 0% { transform: scale(0.5);