diff options
Diffstat (limited to 'FrontEnd/src/components/hooks')
-rw-r--r-- | FrontEnd/src/components/hooks/index.ts | 5 | ||||
-rw-r--r-- | FrontEnd/src/components/hooks/responsive.ts | 7 | ||||
-rw-r--r-- | FrontEnd/src/components/hooks/useAutoUnsubscribePromise.ts | 24 | ||||
-rw-r--r-- | FrontEnd/src/components/hooks/useClickOutside.ts | 38 | ||||
-rw-r--r-- | FrontEnd/src/components/hooks/useScrollToBottom.ts | 44 | ||||
-rw-r--r-- | FrontEnd/src/components/hooks/useWindowLeave.ts | 22 |
6 files changed, 140 insertions, 0 deletions
diff --git a/FrontEnd/src/components/hooks/index.ts b/FrontEnd/src/components/hooks/index.ts new file mode 100644 index 00000000..98ce729e --- /dev/null +++ b/FrontEnd/src/components/hooks/index.ts @@ -0,0 +1,5 @@ +export { useMobile } from "./responsive"; +export { default as useClickOutside } from "./useClickOutside"; +export { default as useScrollToBottom } from "./useScrollToBottom"; +export { default as useWindowLeave } from "./useWindowLeave"; +export { default as useAutoUnsubscribePromise } from "./useAutoUnsubscribePromise"; diff --git a/FrontEnd/src/components/hooks/responsive.ts b/FrontEnd/src/components/hooks/responsive.ts new file mode 100644 index 00000000..42c134ef --- /dev/null +++ b/FrontEnd/src/components/hooks/responsive.ts @@ -0,0 +1,7 @@ +import { useMediaQuery } from "react-responsive"; + +import { breakpoints } from "../breakpoints"; + +export function useMobile(onChange?: (mobile: boolean) => void): boolean { + return useMediaQuery({ maxWidth: breakpoints.sm }, undefined, onChange); +} diff --git a/FrontEnd/src/components/hooks/useAutoUnsubscribePromise.ts b/FrontEnd/src/components/hooks/useAutoUnsubscribePromise.ts new file mode 100644 index 00000000..01c5a1db --- /dev/null +++ b/FrontEnd/src/components/hooks/useAutoUnsubscribePromise.ts @@ -0,0 +1,24 @@ +import { useEffect, DependencyList } from "react"; + +export default function useAutoUnsubscribePromise<T>( + promiseGenerator: () => Promise<T> | null | undefined, + resultHandler: (data: T) => void, + dependencies?: DependencyList | undefined, +) { + useEffect(() => { + let subscribe = true; + const promise = promiseGenerator(); + if (promise) { + void promise.then((data) => { + if (subscribe) { + resultHandler(data); + } + }); + + return () => { + subscribe = false; + }; + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [promiseGenerator, resultHandler, ...(dependencies ?? [])]); +} diff --git a/FrontEnd/src/components/hooks/useClickOutside.ts b/FrontEnd/src/components/hooks/useClickOutside.ts new file mode 100644 index 00000000..828ce7e3 --- /dev/null +++ b/FrontEnd/src/components/hooks/useClickOutside.ts @@ -0,0 +1,38 @@ +import { useRef, useEffect } from "react"; + +export default function useClickOutside( + element: HTMLElement | null | undefined, + onClickOutside: () => void, + nextTick?: boolean, +): void { + const onClickOutsideRef = useRef<() => void>(onClickOutside); + + useEffect(() => { + onClickOutsideRef.current = onClickOutside; + }, [onClickOutside]); + + useEffect(() => { + if (element != null) { + const handler = (event: MouseEvent): void => { + let e: HTMLElement | null = event.target as HTMLElement; + while (e) { + if (e == element) { + return; + } + e = e.parentElement; + } + onClickOutsideRef.current(); + }; + if (nextTick) { + setTimeout(() => { + document.addEventListener("click", handler); + }); + } else { + document.addEventListener("click", handler); + } + return () => { + document.removeEventListener("click", handler); + }; + } + }, [element, nextTick]); +} diff --git a/FrontEnd/src/components/hooks/useScrollToBottom.ts b/FrontEnd/src/components/hooks/useScrollToBottom.ts new file mode 100644 index 00000000..79fcda16 --- /dev/null +++ b/FrontEnd/src/components/hooks/useScrollToBottom.ts @@ -0,0 +1,44 @@ +import { useRef, useEffect } from "react"; +import { fromEvent, filter, throttleTime } from "rxjs"; + +function useScrollToBottom( + handler: () => void, + enable = true, + option = { + maxOffset: 5, + throttle: 1000, + }, +): void { + const handlerRef = useRef<(() => void) | null>(null); + + useEffect(() => { + handlerRef.current = handler; + + return () => { + handlerRef.current = null; + }; + }, [handler]); + + useEffect(() => { + const subscription = fromEvent(window, "scroll") + .pipe( + filter( + () => + window.scrollY >= + document.body.scrollHeight - window.innerHeight - option.maxOffset, + ), + throttleTime(option.throttle), + ) + .subscribe(() => { + if (enable) { + handlerRef.current?.(); + } + }); + + return () => { + subscription.unsubscribe(); + }; + }, [enable, option.maxOffset, option.throttle]); +} + +export default useScrollToBottom; diff --git a/FrontEnd/src/components/hooks/useWindowLeave.ts b/FrontEnd/src/components/hooks/useWindowLeave.ts new file mode 100644 index 00000000..ecd999d4 --- /dev/null +++ b/FrontEnd/src/components/hooks/useWindowLeave.ts @@ -0,0 +1,22 @@ +import { useEffect } from "react"; + +import { useC, Text } from "../common"; + +export default function useWindowLeave( + allow: boolean, + message: Text = "timeline.confirmLeave", +) { + const c = useC(); + + useEffect(() => { + if (!allow) { + window.onbeforeunload = () => { + return c(message); + }; + + return () => { + window.onbeforeunload = null; + }; + } + }, [c, allow, message]); +} |