From a623f12e303df7a24c52d58dec25847b83f137ee Mon Sep 17 00:00:00 2001 From: wnhlee <2wheeh@gmail.com> Date: Tue, 30 Apr 2024 18:05:55 +0900 Subject: [PATCH] feat: implement useStickyIOS hook --- ui/src/app/(board)/review/write/page.tsx | 1 + ui/src/hooks/common/use-sticky-ios.ts | 89 ++++++++++++++++++++++++ ui/tailwind.config.ts | 5 ++ 3 files changed, 95 insertions(+) create mode 100644 ui/src/hooks/common/use-sticky-ios.ts diff --git a/ui/src/app/(board)/review/write/page.tsx b/ui/src/app/(board)/review/write/page.tsx index 0e124111..ff1154c1 100644 --- a/ui/src/app/(board)/review/write/page.tsx +++ b/ui/src/app/(board)/review/write/page.tsx @@ -10,6 +10,7 @@ import ReviewTitle from '@/components/review/client/review-title'; import { EditorRefProvider } from '@/context/editor/editor-ref-context'; import { ReviewProvider } from '@/context/review/review-context'; + import { MAX_CONTENT_LENGTH } from '@/lib/constants/review'; const Editor = dynamic(() => import('@/editor'), { diff --git a/ui/src/hooks/common/use-sticky-ios.ts b/ui/src/hooks/common/use-sticky-ios.ts new file mode 100644 index 00000000..0f8e5417 --- /dev/null +++ b/ui/src/hooks/common/use-sticky-ios.ts @@ -0,0 +1,89 @@ +// This is a custom hook to fix the issue with sticky and the soft keyboard on ios. +// on ios, when the soft keyboard is open, the keyboard pushes the content up. +// This ends up getting the sticky element out of the screen. +// As a workaround, we wrap the sticky element with a wrapper, +// set a margin to the sticky element as much as the wrapper is out of the screen. +import { useDebouncedCallback } from './use-debounced-callback'; +import { useCallback, useEffect, useRef } from 'react'; + +const DEBOUNCE_TIME = 150; // ms +const TRANSITION_CLASS = 'animate-slide-down'; + +export function useStickyIOS< + StickyElementType extends HTMLElement = HTMLDivElement, + WrapperElementType extends HTMLElement = HTMLDivElement, +>() { + const stickyRef = useRef(null); + const wrapperRef = useRef(null); + const blurred = useRef(false); + + const stickyOffsetRef = useRef(0); + + const setMargin = () => { + const wrapper = wrapperRef.current; + const sticky = stickyRef.current; + + if (!wrapper || !sticky) { + return; + } + + const newPosition = wrapper.getBoundingClientRect().top; + // do nothing if wrapper remains in the screen + if (newPosition >= 0) { + return; + } + + stickyRef.current.classList.add(TRANSITION_CLASS); + // if wrapper is out of the screen, + // add a margin to show the sticky element + stickyOffsetRef.current = Math.abs(newPosition); + + // set the margin by the new fixed position + sticky.style.marginTop = `${stickyOffsetRef.current}px`; + }; + + const debouncedSetMargin = useDebouncedCallback(setMargin, DEBOUNCE_TIME); + + const resetPosition = useCallback(() => { + if (stickyRef.current) { + stickyRef.current.style.marginTop = '0px'; + stickyRef.current.classList.remove(TRANSITION_CLASS); + } + + if (stickyOffsetRef.current > 0) { + stickyOffsetRef.current = 0; + } + }, []); + + const handleScroll = useCallback(() => { + // put the sticky element back to default position + resetPosition(); + + // When the keyboard gets closed, both the 'scroll' and 'blur' events are fired. + // 'scroll' => 'blur' or 'blur' => 'scroll' + // We skip handling here when 'scroll' is fired after 'blur'. + // Otherwise, wrapper.getBoundingClientRect().top might return incorrect value + // due to keyboard closing animation. + // This would set wrong marginTop with gap above the sticky element. + if (blurred.current) { + blurred.current = false; + return; + } + // show the sticky element with delay + debouncedSetMargin(); + }, [debouncedSetMargin, resetPosition]); + + useEffect(() => { + window.addEventListener('scroll', handleScroll); + return () => { + window.removeEventListener('scroll', handleScroll); + }; + }, [handleScroll]); + + const handleBlur = useCallback(() => { + blurred.current = true; + resetPosition(); + }, [resetPosition]); + + return { handleBlur, stickyRef, wrapperRef }; +} diff --git a/ui/tailwind.config.ts b/ui/tailwind.config.ts index 99552990..4ebc989b 100644 --- a/ui/tailwind.config.ts +++ b/ui/tailwind.config.ts @@ -6,6 +6,7 @@ const config: Config = { extend: { animation: { 'slide-up': 'slide-up 0.3s ease-in-out both', + 'slide-down': 'slide-down 0.3s ease-in-out both', }, keyframes: { shimmer: { @@ -17,6 +18,10 @@ const config: Config = { '0%': { transform: 'translateY(30%)', opacity: '0' }, '100%': { transform: 'translateY(0)', opacity: '1' }, }, + 'slide-down': { + '0%': { transform: 'translateY(-100%)', opacity: '0' }, + '100%': { transform: 'translateY(0)', opacity: '1' }, + }, }, }, },